diff --git a/.devcontainer/devcontainer.dockerfile b/.devcontainer/devcontainer.dockerfile new file mode 100644 index 0000000000..3b16107e9e --- /dev/null +++ b/.devcontainer/devcontainer.dockerfile @@ -0,0 +1,3 @@ +FROM mcr.microsoft.com/devcontainers/dotnet:8.0-jammy +# Install the libleveldb-dev package +RUN apt-get update && apt-get install -y libleveldb-dev diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..5e9bdf6374 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,17 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet +{ + "name": "C# (.NET)", + "build": { + // Path is relative to the devcontainer.json file. + "dockerfile": "devcontainer.dockerfile" + }, + "postCreateCommand": "dotnet build", + "customizations": { + "vscode": { + "extensions": [ + "ms-dotnettools.csdevkit" + ] + } + } +} diff --git a/.editorconfig b/.editorconfig index 175ec7b14e..c8247e5496 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,14 +6,305 @@ # dotnet tool update -g dotnet-format # remember to have: git config --global core.autocrlf false #(which is usually default) +# top-most EditorConfig file root = true -# Every file - +# Don't use tabs for indentation. [*] +indent_style = space insert_final_newline = true trim_trailing_whitespace = true charset = utf-8 end_of_line = lf +# (Please don't specify an indent_size here; that has too many unintended consequences.) +spelling_exclusion_path = SpellingExclusions.dic + +# Code files +[*.{cs,csx,vb,vbx}] +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +# XML project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 + +# XML config files +[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +indent_size = 2 + +# JSON files +[*.json] +indent_size = 2 + +# Powershell files +[*.ps1] +indent_size = 2 + +# Shell script files +[*.sh] +end_of_line = lf +indent_size = 2 + +# Dotnet code style settings: +[*.{cs,vb}] +# Member can be made 'readonly' +csharp_style_prefer_readonly_struct_member = true +dotnet_diagnostic.IDE0251.severity = warning +dotnet_diagnostic.IDE0044.severity = warning + dotnet_diagnostic.CS1591.severity = silent + +# Sort using and Import directives with System.* appearing first +dotnet_sort_system_directives_first = false +dotnet_separate_import_directive_groups = false + +# Avoid "this." and "Me." if not necessary +dotnet_style_qualification_for_field = false:warning +dotnet_style_qualification_for_property = false:warning +dotnet_style_qualification_for_method = false:warning +dotnet_style_qualification_for_event = false:warning + +# Use language keywords instead of framework type names for type references +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion + +# Suggest more modern language features when available +dotnet_style_object_initializer = true:warning +dotnet_style_collection_initializer = true:warning +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion + +# Whitespace options +dotnet_style_allow_multiple_blank_lines_experimental = false + +# Non-private static fields are PascalCase +dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields +dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style + +dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field +dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected +dotnet_naming_symbols.non_private_static_fields.required_modifiers = static + +dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case + +# Non-private readonly fields are PascalCase +dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.symbols = non_private_readonly_fields +dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.style = non_private_readonly_field_style + +dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field +dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected +dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly + +dotnet_naming_style.non_private_readonly_field_style.capitalization = pascal_case + +# Constants are PascalCase +dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants +dotnet_naming_rule.constants_should_be_pascal_case.style = constant_style + +dotnet_naming_symbols.constants.applicable_kinds = field, local +dotnet_naming_symbols.constants.required_modifiers = const + +dotnet_naming_style.constant_style.capitalization = pascal_case + +# Static fields are camelCase and start with s_ +dotnet_naming_rule.static_fields_should_be_camel_case.severity = suggestion +dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields +dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style + +dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.required_modifiers = static + +dotnet_naming_style.static_field_style.capitalization = camel_case +dotnet_naming_style.static_field_style.required_prefix = s_ + +# Instance fields are camelCase and start with _ +dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion +dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields +dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style + +dotnet_naming_symbols.instance_fields.applicable_kinds = field + +dotnet_naming_style.instance_field_style.capitalization = camel_case +dotnet_naming_style.instance_field_style.required_prefix = _ + +# Locals and parameters are camelCase +dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion +dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters +dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style + +dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local + +dotnet_naming_style.camel_case_style.capitalization = camel_case + +# Local functions are PascalCase +dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions +dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style + +dotnet_naming_symbols.local_functions.applicable_kinds = local_function + +dotnet_naming_style.local_function_style.capitalization = pascal_case + +# By default, name items with PascalCase +dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members +dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style + +dotnet_naming_symbols.all_members.applicable_kinds = * + +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +file_header_template = Copyright (C) 2015-2024 The Neo Project.\n\n{fileName} file belongs to the neo project and is free\nsoftware distributed under the MIT software license, see the\naccompanying file LICENSE in the main directory of the\nrepository or http://www.opensource.org/licenses/mit-license.php\nfor more details.\n\nRedistribution and use in source and binary forms with or without\nmodifications are permitted. + +# Require file header +dotnet_diagnostic.IDE0073.severity = error + +# RS0016: Only enable if API files are present +dotnet_public_api_analyzer.require_api_files = true + +# IDE0055: Fix formatting +# Workaround for https://github.com/dotnet/roslyn/issues/70570 +dotnet_diagnostic.IDE0055.severity = warning + +# CSharp code style settings: +[*.cs] +# Newline settings +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_switch_labels = true +csharp_indent_labels = flush_left + +# Whitespace options +csharp_style_allow_embedded_statements_on_same_line_experimental = false +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = false +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = false + +# Prefer "var" everywhere +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion +csharp_style_var_elsewhere = true:suggestion + +# Prefer method-like constructs to have a block body +csharp_style_expression_bodied_methods = false:none +csharp_style_expression_bodied_constructors = false:none +csharp_style_expression_bodied_operators = false:none + +# Prefer property-like constructs to have an expression-body +csharp_style_expression_bodied_properties = true:none +csharp_style_expression_bodied_indexers = true:none +csharp_style_expression_bodied_accessors = true:none + +# Suggest more modern language features when available +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion +csharp_style_prefer_extended_property_pattern = true:suggestion + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = do_not_ignore +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Blocks are allowed +csharp_prefer_braces = true:silent +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +# IDE0060: Remove unused parameter +dotnet_diagnostic.IDE0060.severity = none + +[src/{Analyzers,CodeStyle,Features,Workspaces,EditorFeatures,VisualStudio}/**/*.{cs,vb}] + +# Use collection expression for array +dotnet_diagnostic.IDE0300.severity = warning + +# Avoid "this." and "Me." if not necessary +dotnet_diagnostic.IDE0003.severity = warning +dotnet_diagnostic.IDE0009.severity = warning + +# IDE0011: Add braces +csharp_prefer_braces = when_multiline:warning +# NOTE: We need the below severity entry for Add Braces due to https://github.com/dotnet/roslyn/issues/44201 +dotnet_diagnostic.IDE0011.severity = warning + +# IDE0040: Add accessibility modifiers +dotnet_diagnostic.IDE0040.severity = warning + +# IDE0052: Remove unread private member +dotnet_diagnostic.IDE0052.severity = warning + +# IDE0059: Unnecessary assignment to a value +dotnet_diagnostic.IDE0059.severity = warning + +# CA1012: Abstract types should not have public constructors +dotnet_diagnostic.CA1012.severity = warning + +# CA1822: Make member static +dotnet_diagnostic.CA1822.severity = warning + +# Prefer "var" everywhere +dotnet_diagnostic.IDE0007.severity = warning +csharp_style_var_for_built_in_types = true:warning +csharp_style_var_when_type_is_apparent = true:warning +csharp_style_var_elsewhere = true:warning + +# csharp_style_allow_embedded_statements_on_same_line_experimental +dotnet_diagnostic.IDE2001.severity = warning + +# csharp_style_allow_blank_lines_between_consecutive_braces_experimental +dotnet_diagnostic.IDE2002.severity = warning + +# csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental +dotnet_diagnostic.IDE2004.severity = warning + +# csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental +dotnet_diagnostic.IDE2005.severity = warning + +# csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental +dotnet_diagnostic.IDE2006.severity = warning + +[src/{VisualStudio}/**/*.{cs,vb}] +# CA1822: Make member static +# There is a risk of accidentally breaking an internal API that partners rely on though IVT. +dotnet_code_quality.CA1822.api_surface = private diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..95c6696127 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,69 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text eol=lf +*.cs eol=lf +*.csproj eol=lf +*.props eol=lf +*.json eol=lf + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +*.sln text eol=crlf +#*.csproj text eol=crlf +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +*.jpg binary +*.png binary +*.gif binary +*.ico binary +*.zip binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.github/ISSUE_TEMPLATE/feature-or-enhancement-request.md b/.github/ISSUE_TEMPLATE/feature-or-enhancement-request.md index 4c2e7a8977..9b98d24f99 100644 --- a/.github/ISSUE_TEMPLATE/feature-or-enhancement-request.md +++ b/.github/ISSUE_TEMPLATE/feature-or-enhancement-request.md @@ -12,10 +12,6 @@ A summary of the problem you want to solve or metric you want to improve **Do you have any solution you want to propose?** A clear and concise description of what you expect with this change. -**Neo Version** -- Neo 2 -- Neo 3 - **Where in the software does this update applies to?** - Compiler - Consensus diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..4ebc00ed19 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,37 @@ +# Description + + + +Fixes # (issue) + +## Type of change + + + +- [ ] Optimization (the change is only an optimization) +- [ ] Style (the change is only a code style for better maintenance or standard purpose) +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] This change requires a documentation update + +# How Has This Been Tested? + + + +- [ ] Test A +- [ ] Test B + +**Test Configuration**: + + +# Checklist: + +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published in downstream modules diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 05cc47a22f..e12760e019 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,71 +6,172 @@ on: pull_request: env: - DOTNET_VERSION: 7.0.x + DOTNET_VERSION: 8.0.x jobs: + Format: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + - name: Check Format (*.cs) + run: dotnet format --verify-no-changes --verbosity diagnostic + + Build-Test-Neo-Cli: + needs: [Format] + timeout-minutes: 15 + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + - name: Build (Neo.CLI) + run: dotnet build ./src/Neo.CLI --output ./out/Neo.CLI + + - name: Install dependencies + run: | + sudo apt-get install libleveldb-dev expect + find ./out -name 'config.json' | xargs perl -pi -e 's|LevelDBStore|MemoryStore|g' + + - name: Run tests with expect + run: expect ./scripts/Neo.CLI/test-neo-cli.expect + Test: - strategy: + needs: [Format] + timeout-minutes: 15 + strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] runs-on: ${{ matrix.os }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + - name: Setup .NET - uses: actions/setup-dotnet@v2 + uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} - - name: Check format - if: matrix.os == 'ubuntu-latest' - run: | - dotnet format --verify-no-changes --verbosity diagnostic + - name: Test + if: matrix.os != 'ubuntu-latest' + run: | + dotnet sln neo.sln remove ./tests/Neo.Plugins.Storage.Tests/Neo.Plugins.Storage.Tests.csproj + dotnet test + + - name: Test for coverall + if: matrix.os == 'ubuntu-latest' run: | - find tests -name *.csproj | xargs -I % dotnet add % package coverlet.msbuild - dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=lcov /p:CoverletOutput=${GITHUB_WORKSPACE}/coverage/lcov + sudo apt-get --assume-yes install libleveldb-dev librocksdb-dev + + dotnet test ./tests/Neo.Cryptography.BLS12_381.Tests --output ./bin/tests/Neo.Cryptography.BLS12_381.Tests + dotnet test ./tests/Neo.ConsoleService.Tests --output ./bin/tests/Neo.ConsoleService.Tests + dotnet test ./tests/Neo.UnitTests --output ./bin/tests/Neo.UnitTests + dotnet test ./tests/Neo.VM.Tests --output ./bin/tests/Neo.VM.Tests + dotnet test ./tests/Neo.Json.UnitTests --output ./bin/tests/Neo.Json.UnitTests + + # Plugins + dotnet test ./tests/Neo.Cryptography.MPTTrie.Tests --output ./bin/tests/Neo.Cryptography.MPTTrie.Tests + dotnet test ./tests/Neo.Network.RPC.Tests --output ./bin/tests/Neo.Network.RPC.Tests + dotnet test ./tests/Neo.Plugins.OracleService.Tests --output ./bin/tests/Neo.Plugins.OracleService.Tests + dotnet test ./tests/Neo.Plugins.RpcServer.Tests --output ./bin/tests/Neo.Plugins.RpcServer.Tests + dotnet test ./tests/Neo.Plugins.Storage.Tests --output ./bin/tests/Neo.Plugins.Storage.Tests + - name: Coveralls if: matrix.os == 'ubuntu-latest' - uses: coverallsapp/github-action@master + uses: coverallsapp/github-action@v2.3.0 with: github-token: ${{ secrets.GITHUB_TOKEN }} + files: + ${{ github.workspace }}/tests/Neo.Cryptography.BLS12_381.Tests/TestResults/coverage.info + ${{ github.workspace }}/tests/Neo.ConsoleService.Tests/TestResults/coverage.info + ${{ github.workspace }}/tests/Neo.UnitTests/TestResults/coverage.info + ${{ github.workspace }}/tests/Neo.VM.Tests/TestResults/coverage.info + ${{ github.workspace }}/tests/Neo.Json.UnitTests/TestResults/coverage.info + ${{ github.workspace }}/tests/Neo.Cryptography.MPTTrie.Tests/TestResults/coverage.info + ${{ github.workspace }}/tests/Neo.Network.RPC.Tests/TestResults/coverage.info + ${{ github.workspace }}/tests/Neo.Plugins.OracleService.Tests/TestResults/coverage.info + ${{ github.workspace }}/tests/Neo.Plugins.RpcServer.Tests/TestResults/coverage.info + ${{ github.workspace }}/tests/Neo.Plugins.Storage.Tests/TestResults/coverage.info - PublishMyGet: + PublishPackage: if: github.ref == 'refs/heads/master' && startsWith(github.repository, 'neo-project/') - needs: Test + needs: [Test] runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Setup .NET - uses: actions/setup-dotnet@v2 + + - name: Setup .NET Core + uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} - - name: Pack with dotnet - run: git rev-list --count HEAD |xargs printf "CI%05d" |xargs dotnet pack -c Debug -o out --include-source --version-suffix - - name: Publish to MyGet - run: dotnet nuget push out/*.nupkg -s https://www.myget.org/F/neo/api/v2/package -k ${MYGET_TOKEN} -ss https://www.myget.org/F/neo/symbols/api/v2/package -sk ${MYGET_TOKEN} - env: - MYGET_TOKEN: ${{ secrets.MYGET_TOKEN }} + + - name: Set Version + run: git rev-list --count HEAD | xargs printf 'CI%05d' | xargs -I{} echo 'VERSION_SUFFIX={}' >> $GITHUB_ENV + + - name : Pack (Neo) + run: | + dotnet pack \ + --configuration Release \ + --output ./out \ + --version-suffix ${{ env.VERSION_SUFFIX }} + + - name: Remove Unwanted Files + working-directory: ./out + run: | + rm -v Neo.CLI* + rm -v Neo.GUI* + + - name: Publish to Github Packages + working-directory: ./out + run: | + dotnet nuget push * \ + --source https://nuget.pkg.github.com/neo-project/index.json \ + --api-key "${{ secrets.GITHUB_TOKEN }}" \ + --disable-buffering \ + --no-service-endpoint; + + - name: Publish to myGet + working-directory: ./out + run: | + dotnet nuget push * \ + --source https://www.myget.org/F/neo/api/v3/index.json \ + --api-key "${{ secrets.MYGET_TOKEN }}" \ + --disable-buffering \ + --no-service-endpoint; Release: if: github.ref == 'refs/heads/master' && startsWith(github.repository, 'neo-project/') - needs: Test + needs: [Test] runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + - name: Get version id: get_version run: | sudo apt install xmlstarlet find src -name Directory.Build.props | xargs xmlstarlet sel -N i=http://schemas.microsoft.com/developer/msbuild/2003 -t -v "concat('::set-output name=version::v',//i:VersionPrefix/text())" | xargs echo + - name: Check tag id: check_tag run: curl -s -I ${{ format('https://github.com/{0}/releases/tag/{1}', github.repository, steps.get_version.outputs.version) }} | head -n 1 | cut -d$' ' -f2 | xargs printf "::set-output name=statusCode::%s" | xargs echo + - name: Create release if: steps.check_tag.outputs.statusCode == '404' id: create_release @@ -81,15 +182,65 @@ jobs: tag_name: ${{ steps.get_version.outputs.version }} release_name: ${{ steps.get_version.outputs.version }} prerelease: ${{ contains(steps.get_version.outputs.version, '-') }} + - name: Setup .NET if: steps.check_tag.outputs.statusCode == '404' - uses: actions/setup-dotnet@v2 + uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} + + - name : Pack (Neo) + if: steps.check_tag.outputs.statusCode == '404' + run: | + dotnet pack ./src/Neo \ + --configuration Release \ + --output ./out + + - name : Pack (Neo.IO) + if: steps.check_tag.outputs.statusCode == '404' + run: | + dotnet pack ./src/Neo.IO \ + --configuration Release \ + --output ./out + + - name : Pack (Neo.Extensions) + if: steps.check_tag.outputs.statusCode == '404' + run: | + dotnet pack ./src/Neo.Extensions \ + --configuration Release \ + --output ./out + + - name : Pack (Neo.Json) + if: steps.check_tag.outputs.statusCode == '404' + run: | + dotnet pack ./src/Neo.Json \ + --configuration Release \ + --output ./out + + - name : Pack (Neo.VM) + if: steps.check_tag.outputs.statusCode == '404' + run: | + dotnet pack ./src/Neo.VM \ + --configuration Release \ + --output ./out + + - name : Pack (Neo.ConsoleService) + if: steps.check_tag.outputs.statusCode == '404' + run: | + dotnet pack ./src/Neo.ConsoleService \ + --configuration Release \ + --output ./out + + - name : Pack (Neo.Cryptography.BLS12_381) + if: steps.check_tag.outputs.statusCode == '404' + run: | + dotnet pack ./src/Neo.Cryptography.BLS12_381 \ + --configuration Release \ + --output ./out + - name: Publish to NuGet if: steps.check_tag.outputs.statusCode == '404' run: | - dotnet pack -o out -c Release - dotnet nuget push out/*.nupkg -s https://api.nuget.org/v3/index.json -k ${NUGET_TOKEN} + dotnet nuget push out/*.nupkg -s https://api.nuget.org/v3/index.json -k ${NUGET_TOKEN} --skip-duplicate env: NUGET_TOKEN: ${{ secrets.NUGET_TOKEN }} diff --git a/.github/workflows/pkgs-delete.yml b/.github/workflows/pkgs-delete.yml new file mode 100644 index 0000000000..cf3471b551 --- /dev/null +++ b/.github/workflows/pkgs-delete.yml @@ -0,0 +1,128 @@ +name: Package Cleanup + +on: + schedule: + - cron: '0 0 * * *' # Run every day at 24:00 + +jobs: + + delete-myget-pkgs: + name: Delete Old MyGet Packages + runs-on: ubuntu-latest + steps: + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.x' + - name: Install Requests + run: pip install requests + - name: Install Packaging + run: pip install packaging + - name: Delete versions below 3.6.1 + env: + MYGET_FEED: 'neo' + PACKAGE_NAMES: 'Neo.VM,Neo.Json,Neo.IO,Neo,Neo.ConsoleService,Neo.Extensions' # Neo.Cryptography.BLS12_381 version is 0.x + MYGET_API_KEY: ${{ secrets.MYGET_TOKEN }} + run: | + import requests + from packaging import version + import os + + def get_versions(feed, package_name, api_key): + url = f"https://www.myget.org/F/{feed}/api/v2/Packages?$select=Version&$filter=Id eq '{package_name}'&$format=json" + headers = {'Accept': 'application/json'} + response = requests.get(url, headers=headers) + if response.status_code == 200: + versions = response.json()['d']['results'] + return [ver['Version'] for ver in versions] + else: + return [] + + def delete_version(feed, package_name, ver, api_key): + url = f"https://www.myget.org/F/{feed}/api/v2/package/{package_name}/{ver}?hardDelete=true" + headers = {"X-NuGet-ApiKey": api_key} + response = requests.delete(url, headers=headers) + return response.status_code == 200 # Success + + feed = os.environ['MYGET_FEED'] + package_names = os.environ['PACKAGE_NAMES'].split(',') + api_key = os.environ['MYGET_API_KEY'] + + for package_name in package_names: + versions_to_delete = get_versions(feed, package_name, api_key) + for ver in versions_to_delete: + if version.parse(ver.split("-", 1)[0]) >= version.Version("3.6.1"): + print(f"Omited {ver} of package {package_name}.") + continue + if delete_version(feed, package_name, ver, api_key): + print(f"Deleted version {ver} of package {package_name}.") + else: + print(f"Failed to delete version {ver} of package {package_name}.") + + shell: python + + delete-git-pkgs: + name: Delete Old Nuget Packages + runs-on: ubuntu-latest + + steps: + - name: Delete Neo.Cryptography.BLS12_381 Package + uses: actions/delete-package-versions@v4 + with: + package-name: Neo.Cryptography.BLS12_381 + package-type: nuget + min-versions-to-keep: 3 + delete-only-pre-release-versions: "true" + token: "${{ secrets.GITHUB_TOKEN }}" + + - name: Delete Neo.VM Package + uses: actions/delete-package-versions@v4 + with: + package-name: Neo.VM + package-type: nuget + min-versions-to-keep: 3 + delete-only-pre-release-versions: "true" + token: "${{ secrets.GITHUB_TOKEN }}" + + - name: Delete Neo.Json Package + uses: actions/delete-package-versions@v4 + with: + package-name: Neo.Json + package-type: nuget + min-versions-to-keep: 3 + delete-only-pre-release-versions: "true" + token: "${{ secrets.GITHUB_TOKEN }}" + + - name: Delete Neo.IO Package + uses: actions/delete-package-versions@v4 + with: + package-name: Neo.IO + package-type: nuget + min-versions-to-keep: 3 + delete-only-pre-release-versions: "true" + token: "${{ secrets.GITHUB_TOKEN }}" + + - name: Delete Neo Package + uses: actions/delete-package-versions@v4 + with: + package-name: Neo + package-type: nuget + min-versions-to-keep: 3 + delete-only-pre-release-versions: "true" + token: "${{ secrets.GITHUB_TOKEN }}" + - name: Delete Neo.ConsoleService Package + uses: actions/delete-package-versions@v4 + with: + package-name: Neo.ConsoleService + package-type: nuget + min-versions-to-keep: 3 + delete-only-pre-release-versions: "true" + token: "${{ secrets.GITHUB_TOKEN }}" + - name: Delete Neo.Extensions Package + uses: actions/delete-package-versions@v4 + with: + package-name: Neo.Extensions + package-type: nuget + min-versions-to-keep: 3 + delete-only-pre-release-versions: "true" + token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.gitignore b/.gitignore index cb49b769de..1358af3bde 100644 --- a/.gitignore +++ b/.gitignore @@ -256,3 +256,4 @@ paket-files/ PublishProfiles /.vscode +launchSettings.json diff --git a/.neo/README.md b/.neo/README.md new file mode 100644 index 0000000000..c3738b18b9 --- /dev/null +++ b/.neo/README.md @@ -0,0 +1,25 @@ +## Overview +This repository contain main classes of the [Neo](https://www.neo.org) blockchain. +Visit the [documentation](https://docs.neo.org/docs/en-us/index.html) to get started. + +## Related projects +Code references are provided for all platform building blocks. That includes the base library, the VM, a command line application and the compiler. + +- [neo:](https://github.com/neo-project/neo/) Neo core library, contains base classes, including ledger, p2p and IO modules. +- [neo-modules:](https://github.com/neo-project/neo-modules/) Neo modules include additional tools and plugins to be used with Neo. +- [neo-devpack-dotnet:](https://github.com/neo-project/neo-devpack-dotnet/) These are the official tools used to convert a C# smart-contract into a *neo executable file*. + +## Opening a new issue +Please feel free to create new issues to suggest features or ask questions. + +- [Feature request](https://github.com/neo-project/neo/issues/new?assignees=&labels=discussion&template=feature-or-enhancement-request.md&title=) +- [Bug report](https://github.com/neo-project/neo/issues/new?assignees=&labels=&template=bug_report.md&title=) +- [Questions](https://github.com/neo-project/neo/issues/new?assignees=&labels=question&template=questions.md&title=) + +If you found a security issue, please refer to our [security policy](https://github.com/neo-project/neo/security/policy). + +## Bounty program +You can be rewarded by finding security issues. Please refer to our [bounty program page](https://neo.org/bounty) for more information. + +## License +The NEO project is licensed under the [MIT license](http://www.opensource.org/licenses/mit-license.php). diff --git a/.neo/neo.png b/.neo/neo.png new file mode 100644 index 0000000000..1a71de07eb Binary files /dev/null and b/.neo/neo.png differ diff --git a/NuGet.Config b/NuGet.Config index c06788942f..53fc635420 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -2,7 +2,8 @@ + - \ No newline at end of file + diff --git a/README.md b/README.md index 39d02a7fba..407bd6a837 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,9 @@
Neo · - Neo VM - · Neo Modules · Neo DevPack - · - Neo Node

@@ -92,7 +88,11 @@

- +

+ + Open in GitHub Codespaces. + +

## Table of Contents @@ -105,8 +105,8 @@ ## Overview This repository contain main classes of the -[Neo](https://www.neo.org) blockchain. -Visit the [documentation](https://docs.neo.org/docs/en-us/index.html) to get started. +[Neo](https://neo.org) blockchain. +Visit the [tutorials](https://developers.neo.org) to get started. ## Project structure @@ -130,8 +130,6 @@ An overview of the project folders can be seen below. Code references are provided for all platform building blocks. That includes the base library, the VM, a command line application and the compiler. * [neo:](https://github.com/neo-project/neo/) Neo core library, contains base classes, including ledger, p2p and IO modules. -* [neo-vm:](https://github.com/neo-project/neo-vm/) Neo Virtual Machine is a decoupled VM that Neo uses to execute its scripts. It also uses the `InteropService` layer to extend its functionalities. -* [neo-node:](https://github.com/neo-project/neo-node/) Executable version of the Neo library, exposing features using a command line application or GUI. * [neo-modules:](https://github.com/neo-project/neo-modules/) Neo modules include additional tools and plugins to be used with Neo. * [neo-devpack-dotnet:](https://github.com/neo-project/neo-devpack-dotnet/) These are the official tools used to convert a C# smart-contract into a *neo executable file*. diff --git a/SpellingExclusions.dic b/SpellingExclusions.dic new file mode 100644 index 0000000000..e69de29bb2 diff --git a/benchmarks/Neo.Benchmarks/Benchmarks.cs b/benchmarks/Neo.Benchmarks/Benchmarks.cs index 50a256e372..081f806622 100644 --- a/benchmarks/Neo.Benchmarks/Benchmarks.cs +++ b/benchmarks/Neo.Benchmarks/Benchmarks.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Benchmarks.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Neo.Network.P2P.Payloads; using Neo.SmartContract; using Neo.VM; @@ -7,8 +18,8 @@ namespace Neo; static class Benchmarks { - private static readonly ProtocolSettings protocol = ProtocolSettings.Default; - private static readonly NeoSystem system = new(protocol); + private static readonly ProtocolSettings protocol = ProtocolSettings.Load("config.json"); + private static readonly NeoSystem system = new(protocol, (string)null); public static void NeoIssue2725() { @@ -62,7 +73,7 @@ private static void Run(string name, string poc) Stopwatch stopwatch = Stopwatch.StartNew(); engine.Execute(); stopwatch.Stop(); - Debug.Assert(engine.State == VMState.HALT); + Debug.Assert(engine.State == VMState.FAULT); Console.WriteLine($"Benchmark: {name},\tTime: {stopwatch.Elapsed}"); } } diff --git a/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj b/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj index 12b6e861e4..476ef93060 100644 --- a/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj +++ b/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj @@ -1,15 +1,22 @@ - - - - Exe - net7.0 - Neo - enable - false - - - - - - - + + + + Exe + net8.0 + Neo + enable + false + + + + + + + + + PreserveNewest + PreserveNewest + + + + diff --git a/benchmarks/Neo.Benchmarks/Program.cs b/benchmarks/Neo.Benchmarks/Program.cs index 002fff2761..9d4125bb9f 100644 --- a/benchmarks/Neo.Benchmarks/Program.cs +++ b/benchmarks/Neo.Benchmarks/Program.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Program.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Neo; using System.Reflection; diff --git a/benchmarks/Neo.Benchmarks/config.json b/benchmarks/Neo.Benchmarks/config.json new file mode 100644 index 0000000000..01471e4c76 --- /dev/null +++ b/benchmarks/Neo.Benchmarks/config.json @@ -0,0 +1,71 @@ +{ + "ApplicationConfiguration": { + "Logger": { + "Path": "Logs", + "ConsoleOutput": false, + "Active": false + }, + "Storage": { + "Engine": "LevelDBStore", // Candidates [MemoryStore, LevelDBStore, RocksDBStore] + "Path": "Data_LevelDB_{0}" // {0} is a placeholder for the network id + }, + "P2P": { + "Port": 10333, + "MinDesiredConnections": 10, + "MaxConnections": 40, + "MaxConnectionsPerAddress": 3 + }, + "UnlockWallet": { + "Path": "", + "Password": "", + "IsActive": false + }, + "Contracts": { + "NeoNameService": "0x50ac1c37690cc2cfc594472833cf57505d5f46de" + } + }, + "ProtocolConfiguration": { + "Network": 860833102, + "AddressVersion": 53, + "MillisecondsPerBlock": 15000, + "MaxTransactionsPerBlock": 512, + "MemoryPoolMaxTransactions": 50000, + "MaxTraceableBlocks": 2102400, + "Hardforks": { + "HF_Aspidochelone": 1730000, + "HF_Basilisk": 4120000 + }, + "InitialGasDistribution": 5200000000000000, + "ValidatorsCount": 7, + "StandbyCommittee": [ + "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", + "02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093", + "03b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a", + "02ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba554", + "024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d", + "02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e", + "02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70", + "023a36c72844610b4d34d1968662424011bf783ca9d984efa19a20babf5582f3fe", + "03708b860c1de5d87f5b151a12c2a99feebd2e8b315ee8e7cf8aa19692a9e18379", + "03c6aa6e12638b36e88adc1ccdceac4db9929575c3e03576c617c49cce7114a050", + "03204223f8c86b8cd5c89ef12e4f0dbb314172e9241e30c9ef2293790793537cf0", + "02a62c915cf19c7f19a50ec217e79fac2439bbaad658493de0c7d8ffa92ab0aa62", + "03409f31f0d66bdc2f70a9730b66fe186658f84a8018204db01c106edc36553cd0", + "0288342b141c30dc8ffcde0204929bb46aed5756b41ef4a56778d15ada8f0c6654", + "020f2887f41474cfeb11fd262e982051c1541418137c02a0f4961af911045de639", + "0222038884bbd1d8ff109ed3bdef3542e768eef76c1247aea8bc8171f532928c30", + "03d281b42002647f0113f36c7b8efb30db66078dfaaa9ab3ff76d043a98d512fde", + "02504acbc1f4b3bdad1d86d6e1a08603771db135a73e61c9d565ae06a1938cd2ad", + "0226933336f1b75baa42d42b71d9091508b638046d19abd67f4e119bf64a7cfb4d", + "03cdcea66032b82f5c30450e381e5295cae85c5e6943af716cc6b646352a6067dc", + "02cd5a5547119e24feaa7c2a0f37b8c9366216bab7054de0065c9be42084003c8a" + ], + "SeedList": [ + "seed1.neo.org:10333", + "seed2.neo.org:10333", + "seed3.neo.org:10333", + "seed4.neo.org:10333", + "seed5.neo.org:10333" + ] + } +} diff --git a/benchmarks/Neo.VM.Benchmarks/Benchmarks.cs b/benchmarks/Neo.VM.Benchmarks/Benchmarks.cs new file mode 100644 index 0000000000..6eab691a7d --- /dev/null +++ b/benchmarks/Neo.VM.Benchmarks/Benchmarks.cs @@ -0,0 +1,112 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Benchmarks.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Diagnostics; + +namespace Neo.VM +{ + public static class Benchmarks + { + public static void NeoIssue2528() + { + // https://github.com/neo-project/neo/issues/2528 + // L01: INITSLOT 1, 0 + // L02: NEWARRAY0 + // L03: DUP + // L04: DUP + // L05: PUSHINT16 2043 + // L06: STLOC 0 + // L07: PUSH1 + // L08: PACK + // L09: LDLOC 0 + // L10: DEC + // L11: STLOC 0 + // L12: LDLOC 0 + // L13: JMPIF_L L07 + // L14: PUSH1 + // L15: PACK + // L16: APPEND + // L17: PUSHINT32 38000 + // L18: STLOC 0 + // L19: PUSH0 + // L20: PICKITEM + // L21: LDLOC 0 + // L22: DEC + // L23: STLOC 0 + // L24: LDLOC 0 + // L25: JMPIF_L L19 + // L26: DROP + Run(nameof(NeoIssue2528), "VwEAwkpKAfsHdwARwG8AnXcAbwAl9////xHAzwJwlAAAdwAQzm8AnXcAbwAl9////0U="); + } + + public static void NeoVMIssue418() + { + // https://github.com/neo-project/neo-vm/issues/418 + // L00: NEWARRAY0 + // L01: PUSH0 + // L02: PICK + // L03: PUSH1 + // L04: PACK + // L05: PUSH1 + // L06: PICK + // L07: PUSH1 + // L08: PACK + // L09: INITSSLOT 1 + // L10: PUSHINT16 510 + // L11: DEC + // L12: STSFLD0 + // L13: PUSH1 + // L14: PICK + // L15: PUSH1 + // L16: PICK + // L17: PUSH2 + // L18: PACK + // L19: REVERSE3 + // L20: PUSH2 + // L21: PACK + // L22: LDSFLD0 + // L23: DUP + // L24: JMPIF L11 + // L25: DROP + // L26: ROT + // L27: DROP + Run(nameof(NeoVMIssue418), "whBNEcARTRHAVgEB/gGdYBFNEU0SwFMSwFhKJPNFUUU="); + } + + public static void NeoIssue2723() + { + // L00: INITSSLOT 1 + // L01: PUSHINT32 130000 + // L02: STSFLD 0 + // L03: PUSHINT32 1048576 + // L04: NEWBUFFER + // L05: DROP + // L06: LDSFLD 0 + // L07: DEC + // L08: DUP + // L09: STSFLD 0 + // L10: JMPIF L03 + Run(nameof(NeoIssue2723), "VgEC0PsBAGcAAgAAEACIRV8AnUpnACTz"); + } + + private static void Run(string name, string poc) + { + byte[] script = Convert.FromBase64String(poc); + using ExecutionEngine engine = new(); + engine.LoadScript(script); + Stopwatch stopwatch = Stopwatch.StartNew(); + engine.Execute(); + stopwatch.Stop(); + Debug.Assert(engine.State == VMState.HALT); + Console.WriteLine($"Benchmark: {name},\tTime: {stopwatch.Elapsed}"); + } + } +} diff --git a/benchmarks/Neo.VM.Benchmarks/Neo.VM.Benchmarks.csproj b/benchmarks/Neo.VM.Benchmarks/Neo.VM.Benchmarks.csproj new file mode 100644 index 0000000000..7737c3ec0c --- /dev/null +++ b/benchmarks/Neo.VM.Benchmarks/Neo.VM.Benchmarks.csproj @@ -0,0 +1,16 @@ + + + + Exe + net8.0 + Neo.VM + enable + enable + false + + + + + + + diff --git a/benchmarks/Neo.VM.Benchmarks/Program.cs b/benchmarks/Neo.VM.Benchmarks/Program.cs new file mode 100644 index 0000000000..a9c86f405d --- /dev/null +++ b/benchmarks/Neo.VM.Benchmarks/Program.cs @@ -0,0 +1,18 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Program.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.VM; +using System.Reflection; + +foreach (var method in typeof(Benchmarks).GetMethods(BindingFlags.Public | BindingFlags.Static)) +{ + method.CreateDelegate().Invoke(); +} diff --git a/global.json b/global.json new file mode 100644 index 0000000000..beefe210a1 --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "8.0.202", + "rollForward": "latestFeature", + "allowPrerelease": false + } +} diff --git a/neo.sln b/neo.sln index e705e23988..b0de1c27b1 100644 --- a/neo.sln +++ b/neo.sln @@ -8,7 +8,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.Json", "src\Neo.Json\Ne EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.UnitTests", "tests\Neo.UnitTests\Neo.UnitTests.csproj", "{5B783B30-B422-4C2F-AC22-187A8D1993F4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo.Json.UnitTests", "tests\Neo.Json.UnitTests\Neo.Json.UnitTests.csproj", "{AE6C32EE-8447-4E01-8187-2AE02BB64251}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.Json.UnitTests", "tests\Neo.Json.UnitTests\Neo.Json.UnitTests.csproj", "{AE6C32EE-8447-4E01-8187-2AE02BB64251}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.Benchmarks", "benchmarks\Neo.Benchmarks\Neo.Benchmarks.csproj", "{BCD03521-5F8F-4775-9ADF-FA361480804F}" EndProject @@ -18,6 +18,66 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{EDE05FA8 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{C25EB0B0-0CAC-4CC1-8F36-F9229EFB99EC}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.VM.Benchmarks", "benchmarks\Neo.VM.Benchmarks\Neo.VM.Benchmarks.csproj", "{E83633BA-FCF0-4A1A-B5BC-42000E24D437}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.VM", "src\Neo.VM\Neo.VM.csproj", "{0603710E-E0BA-494C-AA0F-6FB0C8A8C754}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.VM.Tests", "tests\Neo.VM.Tests\Neo.VM.Tests.csproj", "{005F84EB-EA2E-449F-930A-7B4173DDC7EC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.ConsoleService", "src\Neo.ConsoleService\Neo.ConsoleService.csproj", "{9E886812-7243-48D8-BEAF-47AADC11C054}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.GUI", "src\Neo.GUI\Neo.GUI.csproj", "{02ABDE42-9880-43B4-B6F7-8D618602A277}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.CLI", "src\Neo.CLI\Neo.CLI.csproj", "{BDFBE455-4C1F-4FC4-B5FC-1387B93A8687}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.ConsoleService.Tests", "tests\Neo.ConsoleService.Tests\Neo.ConsoleService.Tests.csproj", "{B40F8584-5AFB-452C-AEFA-009C80CC23A9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.Cryptography.BLS12_381", "src\Neo.Cryptography.BLS12_381\Neo.Cryptography.BLS12_381.csproj", "{D48C1FAB-3471-4CA0-8688-25E6F43F2C25}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.Cryptography.BLS12_381.Tests", "tests\Neo.Cryptography.BLS12_381.Tests\Neo.Cryptography.BLS12_381.Tests.csproj", "{387CCF6C-9A26-43F6-A639-0A82E91E10D8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.IO", "src\Neo.IO\Neo.IO.csproj", "{4CDAC1AA-45C6-4157-8D8E-199050433048}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.Extensions", "src\Neo.Extensions\Neo.Extensions.csproj", "{9C5213D6-3833-4570-8AE2-47E9F9017A8F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "plugins", "plugins", "{C2DC830A-327A-42A7-807D-295216D30DBB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.Cryptography.MPTTrie.Tests", "tests\Neo.Cryptography.MPTTrie.Tests\Neo.Cryptography.MPTTrie.Tests.csproj", "{FAF5D8AC-B6B3-4CD4-879D-0E5F6211480F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.Network.RPC.Tests", "tests\Neo.Network.RPC.Tests\Neo.Network.RPC.Tests.csproj", "{0E92F219-1225-4DD0-8C4A-98840985D59C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.Plugins.OracleService.Tests", "tests\Neo.Plugins.OracleService.Tests\Neo.Plugins.OracleService.Tests.csproj", "{5D9764FB-827D-4DDE-84E3-3C05FD8ABC89}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.Plugins.RpcServer.Tests", "tests\Neo.Plugins.RpcServer.Tests\Neo.Plugins.RpcServer.Tests.csproj", "{2CBD2311-BA2E-4921-A000-FDDA59B74958}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.Plugins.Storage.Tests", "tests\Neo.Plugins.Storage.Tests\Neo.Plugins.Storage.Tests.csproj", "{EF01E062-DBBC-47AF-AF3B-9EDEB00CFF7C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{7F257712-D033-47FF-B439-9D4320D06599}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationLogs", "src\Plugins\ApplicationLogs\ApplicationLogs.csproj", "{22E2CE64-080B-4138-885F-7FA74A9159FB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DBFTPlugin", "src\Plugins\DBFTPlugin\DBFTPlugin.csproj", "{4C39E872-FC37-4BFD-AE4C-3E3F0546B726}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LevelDBStore", "src\Plugins\LevelDBStore\LevelDBStore.csproj", "{4C4D8180-9326-486C-AECF-8368BBD0766A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MPTTrie", "src\Plugins\MPTTrie\MPTTrie.csproj", "{80DA3CE7-9770-4F00-9179-0DA91DABFDFA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OracleService", "src\Plugins\OracleService\OracleService.csproj", "{DE0FB77E-3099-4C88-BB7D-BFAED75D813E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RocksDBStore", "src\Plugins\RocksDBStore\RocksDBStore.csproj", "{3DE59148-59D6-4CD3-8086-0BC74E3D4E0B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RpcServer", "src\Plugins\RpcServer\RpcServer.csproj", "{A3941551-E72C-42D7-8C4D-5122CB60D73D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SQLiteWallet", "src\Plugins\SQLiteWallet\SQLiteWallet.csproj", "{F53D5FF0-5D3D-4E8B-A44F-C4C5D9B563B1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StateService", "src\Plugins\StateService\StateService.csproj", "{88975A8D-4797-45A4-BC3E-15962A425A54}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StorageDumper", "src\Plugins\StorageDumper\StorageDumper.csproj", "{FF76D8A4-356B-461A-8471-BC1B83E57BBC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TokensTracker", "src\Plugins\TokensTracker\TokensTracker.csproj", "{5E4947F3-05D3-4806-B0F3-30DAC71B5986}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RpcClient", "src\Plugins\RpcClient\RpcClient.csproj", "{185ADAFC-BFC6-413D-BC2E-97F9FB0A8AF0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -44,6 +104,118 @@ Global {BCD03521-5F8F-4775-9ADF-FA361480804F}.Debug|Any CPU.Build.0 = Debug|Any CPU {BCD03521-5F8F-4775-9ADF-FA361480804F}.Release|Any CPU.ActiveCfg = Release|Any CPU {BCD03521-5F8F-4775-9ADF-FA361480804F}.Release|Any CPU.Build.0 = Release|Any CPU + {E83633BA-FCF0-4A1A-B5BC-42000E24D437}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E83633BA-FCF0-4A1A-B5BC-42000E24D437}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E83633BA-FCF0-4A1A-B5BC-42000E24D437}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E83633BA-FCF0-4A1A-B5BC-42000E24D437}.Release|Any CPU.Build.0 = Release|Any CPU + {0603710E-E0BA-494C-AA0F-6FB0C8A8C754}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0603710E-E0BA-494C-AA0F-6FB0C8A8C754}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0603710E-E0BA-494C-AA0F-6FB0C8A8C754}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0603710E-E0BA-494C-AA0F-6FB0C8A8C754}.Release|Any CPU.Build.0 = Release|Any CPU + {005F84EB-EA2E-449F-930A-7B4173DDC7EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {005F84EB-EA2E-449F-930A-7B4173DDC7EC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {005F84EB-EA2E-449F-930A-7B4173DDC7EC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {005F84EB-EA2E-449F-930A-7B4173DDC7EC}.Release|Any CPU.Build.0 = Release|Any CPU + {9E886812-7243-48D8-BEAF-47AADC11C054}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9E886812-7243-48D8-BEAF-47AADC11C054}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9E886812-7243-48D8-BEAF-47AADC11C054}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9E886812-7243-48D8-BEAF-47AADC11C054}.Release|Any CPU.Build.0 = Release|Any CPU + {02ABDE42-9880-43B4-B6F7-8D618602A277}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {02ABDE42-9880-43B4-B6F7-8D618602A277}.Debug|Any CPU.Build.0 = Debug|Any CPU + {02ABDE42-9880-43B4-B6F7-8D618602A277}.Release|Any CPU.ActiveCfg = Release|Any CPU + {02ABDE42-9880-43B4-B6F7-8D618602A277}.Release|Any CPU.Build.0 = Release|Any CPU + {BDFBE455-4C1F-4FC4-B5FC-1387B93A8687}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BDFBE455-4C1F-4FC4-B5FC-1387B93A8687}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BDFBE455-4C1F-4FC4-B5FC-1387B93A8687}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BDFBE455-4C1F-4FC4-B5FC-1387B93A8687}.Release|Any CPU.Build.0 = Release|Any CPU + {B40F8584-5AFB-452C-AEFA-009C80CC23A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B40F8584-5AFB-452C-AEFA-009C80CC23A9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B40F8584-5AFB-452C-AEFA-009C80CC23A9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B40F8584-5AFB-452C-AEFA-009C80CC23A9}.Release|Any CPU.Build.0 = Release|Any CPU + {D48C1FAB-3471-4CA0-8688-25E6F43F2C25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D48C1FAB-3471-4CA0-8688-25E6F43F2C25}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D48C1FAB-3471-4CA0-8688-25E6F43F2C25}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D48C1FAB-3471-4CA0-8688-25E6F43F2C25}.Release|Any CPU.Build.0 = Release|Any CPU + {387CCF6C-9A26-43F6-A639-0A82E91E10D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {387CCF6C-9A26-43F6-A639-0A82E91E10D8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {387CCF6C-9A26-43F6-A639-0A82E91E10D8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {387CCF6C-9A26-43F6-A639-0A82E91E10D8}.Release|Any CPU.Build.0 = Release|Any CPU + {4CDAC1AA-45C6-4157-8D8E-199050433048}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4CDAC1AA-45C6-4157-8D8E-199050433048}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4CDAC1AA-45C6-4157-8D8E-199050433048}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4CDAC1AA-45C6-4157-8D8E-199050433048}.Release|Any CPU.Build.0 = Release|Any CPU + {9C5213D6-3833-4570-8AE2-47E9F9017A8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9C5213D6-3833-4570-8AE2-47E9F9017A8F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9C5213D6-3833-4570-8AE2-47E9F9017A8F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9C5213D6-3833-4570-8AE2-47E9F9017A8F}.Release|Any CPU.Build.0 = Release|Any CPU + {FAF5D8AC-B6B3-4CD4-879D-0E5F6211480F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FAF5D8AC-B6B3-4CD4-879D-0E5F6211480F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FAF5D8AC-B6B3-4CD4-879D-0E5F6211480F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FAF5D8AC-B6B3-4CD4-879D-0E5F6211480F}.Release|Any CPU.Build.0 = Release|Any CPU + {0E92F219-1225-4DD0-8C4A-98840985D59C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0E92F219-1225-4DD0-8C4A-98840985D59C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E92F219-1225-4DD0-8C4A-98840985D59C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0E92F219-1225-4DD0-8C4A-98840985D59C}.Release|Any CPU.Build.0 = Release|Any CPU + {5D9764FB-827D-4DDE-84E3-3C05FD8ABC89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D9764FB-827D-4DDE-84E3-3C05FD8ABC89}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D9764FB-827D-4DDE-84E3-3C05FD8ABC89}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D9764FB-827D-4DDE-84E3-3C05FD8ABC89}.Release|Any CPU.Build.0 = Release|Any CPU + {2CBD2311-BA2E-4921-A000-FDDA59B74958}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2CBD2311-BA2E-4921-A000-FDDA59B74958}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2CBD2311-BA2E-4921-A000-FDDA59B74958}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2CBD2311-BA2E-4921-A000-FDDA59B74958}.Release|Any CPU.Build.0 = Release|Any CPU + {EF01E062-DBBC-47AF-AF3B-9EDEB00CFF7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EF01E062-DBBC-47AF-AF3B-9EDEB00CFF7C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EF01E062-DBBC-47AF-AF3B-9EDEB00CFF7C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EF01E062-DBBC-47AF-AF3B-9EDEB00CFF7C}.Release|Any CPU.Build.0 = Release|Any CPU + {22E2CE64-080B-4138-885F-7FA74A9159FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {22E2CE64-080B-4138-885F-7FA74A9159FB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {22E2CE64-080B-4138-885F-7FA74A9159FB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {22E2CE64-080B-4138-885F-7FA74A9159FB}.Release|Any CPU.Build.0 = Release|Any CPU + {4C39E872-FC37-4BFD-AE4C-3E3F0546B726}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4C39E872-FC37-4BFD-AE4C-3E3F0546B726}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4C39E872-FC37-4BFD-AE4C-3E3F0546B726}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4C39E872-FC37-4BFD-AE4C-3E3F0546B726}.Release|Any CPU.Build.0 = Release|Any CPU + {4C4D8180-9326-486C-AECF-8368BBD0766A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4C4D8180-9326-486C-AECF-8368BBD0766A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4C4D8180-9326-486C-AECF-8368BBD0766A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4C4D8180-9326-486C-AECF-8368BBD0766A}.Release|Any CPU.Build.0 = Release|Any CPU + {80DA3CE7-9770-4F00-9179-0DA91DABFDFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {80DA3CE7-9770-4F00-9179-0DA91DABFDFA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {80DA3CE7-9770-4F00-9179-0DA91DABFDFA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {80DA3CE7-9770-4F00-9179-0DA91DABFDFA}.Release|Any CPU.Build.0 = Release|Any CPU + {DE0FB77E-3099-4C88-BB7D-BFAED75D813E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE0FB77E-3099-4C88-BB7D-BFAED75D813E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE0FB77E-3099-4C88-BB7D-BFAED75D813E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE0FB77E-3099-4C88-BB7D-BFAED75D813E}.Release|Any CPU.Build.0 = Release|Any CPU + {3DE59148-59D6-4CD3-8086-0BC74E3D4E0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3DE59148-59D6-4CD3-8086-0BC74E3D4E0B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3DE59148-59D6-4CD3-8086-0BC74E3D4E0B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3DE59148-59D6-4CD3-8086-0BC74E3D4E0B}.Release|Any CPU.Build.0 = Release|Any CPU + {A3941551-E72C-42D7-8C4D-5122CB60D73D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A3941551-E72C-42D7-8C4D-5122CB60D73D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A3941551-E72C-42D7-8C4D-5122CB60D73D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A3941551-E72C-42D7-8C4D-5122CB60D73D}.Release|Any CPU.Build.0 = Release|Any CPU + {F53D5FF0-5D3D-4E8B-A44F-C4C5D9B563B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F53D5FF0-5D3D-4E8B-A44F-C4C5D9B563B1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F53D5FF0-5D3D-4E8B-A44F-C4C5D9B563B1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F53D5FF0-5D3D-4E8B-A44F-C4C5D9B563B1}.Release|Any CPU.Build.0 = Release|Any CPU + {88975A8D-4797-45A4-BC3E-15962A425A54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {88975A8D-4797-45A4-BC3E-15962A425A54}.Debug|Any CPU.Build.0 = Debug|Any CPU + {88975A8D-4797-45A4-BC3E-15962A425A54}.Release|Any CPU.ActiveCfg = Release|Any CPU + {88975A8D-4797-45A4-BC3E-15962A425A54}.Release|Any CPU.Build.0 = Release|Any CPU + {FF76D8A4-356B-461A-8471-BC1B83E57BBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FF76D8A4-356B-461A-8471-BC1B83E57BBC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FF76D8A4-356B-461A-8471-BC1B83E57BBC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FF76D8A4-356B-461A-8471-BC1B83E57BBC}.Release|Any CPU.Build.0 = Release|Any CPU + {5E4947F3-05D3-4806-B0F3-30DAC71B5986}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5E4947F3-05D3-4806-B0F3-30DAC71B5986}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5E4947F3-05D3-4806-B0F3-30DAC71B5986}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5E4947F3-05D3-4806-B0F3-30DAC71B5986}.Release|Any CPU.Build.0 = Release|Any CPU + {185ADAFC-BFC6-413D-BC2E-97F9FB0A8AF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {185ADAFC-BFC6-413D-BC2E-97F9FB0A8AF0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {185ADAFC-BFC6-413D-BC2E-97F9FB0A8AF0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {185ADAFC-BFC6-413D-BC2E-97F9FB0A8AF0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -54,6 +226,35 @@ Global {5B783B30-B422-4C2F-AC22-187A8D1993F4} = {EDE05FA8-8E73-4924-BC63-DD117127EEE1} {AE6C32EE-8447-4E01-8187-2AE02BB64251} = {EDE05FA8-8E73-4924-BC63-DD117127EEE1} {BCD03521-5F8F-4775-9ADF-FA361480804F} = {C25EB0B0-0CAC-4CC1-8F36-F9229EFB99EC} + {E83633BA-FCF0-4A1A-B5BC-42000E24D437} = {C25EB0B0-0CAC-4CC1-8F36-F9229EFB99EC} + {0603710E-E0BA-494C-AA0F-6FB0C8A8C754} = {B5339DF7-5D1D-43BA-B332-74B825E1770E} + {005F84EB-EA2E-449F-930A-7B4173DDC7EC} = {EDE05FA8-8E73-4924-BC63-DD117127EEE1} + {9E886812-7243-48D8-BEAF-47AADC11C054} = {B5339DF7-5D1D-43BA-B332-74B825E1770E} + {02ABDE42-9880-43B4-B6F7-8D618602A277} = {B5339DF7-5D1D-43BA-B332-74B825E1770E} + {BDFBE455-4C1F-4FC4-B5FC-1387B93A8687} = {B5339DF7-5D1D-43BA-B332-74B825E1770E} + {B40F8584-5AFB-452C-AEFA-009C80CC23A9} = {EDE05FA8-8E73-4924-BC63-DD117127EEE1} + {D48C1FAB-3471-4CA0-8688-25E6F43F2C25} = {B5339DF7-5D1D-43BA-B332-74B825E1770E} + {387CCF6C-9A26-43F6-A639-0A82E91E10D8} = {EDE05FA8-8E73-4924-BC63-DD117127EEE1} + {4CDAC1AA-45C6-4157-8D8E-199050433048} = {B5339DF7-5D1D-43BA-B332-74B825E1770E} + {9C5213D6-3833-4570-8AE2-47E9F9017A8F} = {B5339DF7-5D1D-43BA-B332-74B825E1770E} + {FAF5D8AC-B6B3-4CD4-879D-0E5F6211480F} = {7F257712-D033-47FF-B439-9D4320D06599} + {0E92F219-1225-4DD0-8C4A-98840985D59C} = {7F257712-D033-47FF-B439-9D4320D06599} + {5D9764FB-827D-4DDE-84E3-3C05FD8ABC89} = {7F257712-D033-47FF-B439-9D4320D06599} + {2CBD2311-BA2E-4921-A000-FDDA59B74958} = {7F257712-D033-47FF-B439-9D4320D06599} + {EF01E062-DBBC-47AF-AF3B-9EDEB00CFF7C} = {7F257712-D033-47FF-B439-9D4320D06599} + {7F257712-D033-47FF-B439-9D4320D06599} = {C2DC830A-327A-42A7-807D-295216D30DBB} + {22E2CE64-080B-4138-885F-7FA74A9159FB} = {C2DC830A-327A-42A7-807D-295216D30DBB} + {4C39E872-FC37-4BFD-AE4C-3E3F0546B726} = {C2DC830A-327A-42A7-807D-295216D30DBB} + {4C4D8180-9326-486C-AECF-8368BBD0766A} = {C2DC830A-327A-42A7-807D-295216D30DBB} + {80DA3CE7-9770-4F00-9179-0DA91DABFDFA} = {C2DC830A-327A-42A7-807D-295216D30DBB} + {DE0FB77E-3099-4C88-BB7D-BFAED75D813E} = {C2DC830A-327A-42A7-807D-295216D30DBB} + {3DE59148-59D6-4CD3-8086-0BC74E3D4E0B} = {C2DC830A-327A-42A7-807D-295216D30DBB} + {A3941551-E72C-42D7-8C4D-5122CB60D73D} = {C2DC830A-327A-42A7-807D-295216D30DBB} + {F53D5FF0-5D3D-4E8B-A44F-C4C5D9B563B1} = {C2DC830A-327A-42A7-807D-295216D30DBB} + {88975A8D-4797-45A4-BC3E-15962A425A54} = {C2DC830A-327A-42A7-807D-295216D30DBB} + {FF76D8A4-356B-461A-8471-BC1B83E57BBC} = {C2DC830A-327A-42A7-807D-295216D30DBB} + {5E4947F3-05D3-4806-B0F3-30DAC71B5986} = {C2DC830A-327A-42A7-807D-295216D30DBB} + {185ADAFC-BFC6-413D-BC2E-97F9FB0A8AF0} = {C2DC830A-327A-42A7-807D-295216D30DBB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BCBA19D9-F868-4C6D-8061-A2B91E06E3EC} diff --git a/scripts/Neo.CLI/test-neo-cli.expect b/scripts/Neo.CLI/test-neo-cli.expect new file mode 100644 index 0000000000..8319c18f8e --- /dev/null +++ b/scripts/Neo.CLI/test-neo-cli.expect @@ -0,0 +1,86 @@ +#!/usr/bin/expect -f +# +# This script uses expect to test neo-cli +# +set timeout 10 + + +# Start neo-cli +spawn dotnet out/Neo.CLI/neo-cli.dll + +# Expect the main input prompt +expect { + "neo> " { } + "error" { exit 2 } + timeout { exit 1 } +} + +# +# Test 'create wallet' +# +send "create wallet test-wallet1.json\n" + +expect { + "password:" { send "asd\n" } + "error" { exit 2 } + timeout { exit 1 } +} + +expect { + "password:" { send "asd\n" } + "error" { exit 2 } + timeout { exit 1 } +} + +expect { + " Address:" { } + "error" { exit 2 } + timeout { exit 1 } +} + + +# +# Test 'create wallet' +# +send "create wallet test-wallet2.json L2ArHTuiDL4FHu4nfyhamrG8XVYB4QyRbmhj7vD6hFMB5iAMSTf6\n" + +expect { + "password:" { send "abcd\n" } + "error" { exit 2 } + timeout { exit 1 } +} + +expect { + "password:" { send "abcd\n" } + "error" { exit 2 } + timeout { exit 1 } +} + +expect { + "NUj249PQg9EMJfAuxKizdJwMG7GSBzYX2Y" { } + "error" { exit 2 } + timeout { exit 1 } +} + +# +# Test 'list address' +# +send "list address\n" + +expect { + "neo> " { } + "error" { exit 2 } + timeout { exit 1 } +} + +# +# Test 'create address' +# +send "create address\n" + +expect { + "neo> " { } + "error" { exit 2 } + timeout { exit 1 } +} +exit 0 diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 2e3fbc31b0..c211ee34aa 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,16 +2,28 @@ - 2015-2023 The Neo Project - 3.6.0 + 2015-2024 The Neo Project + 3.7.4 + 12.0 The Neo Project - net7.0 + neo.png https://github.com/neo-project/neo MIT + README.md git https://github.com/neo-project/neo.git + true + snupkg The Neo Project true + + + + + + + + diff --git a/src/IsExternalInit.cs b/src/IsExternalInit.cs new file mode 100644 index 0000000000..cf4ea93da2 --- /dev/null +++ b/src/IsExternalInit.cs @@ -0,0 +1,28 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// IsExternalInit.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +#if !NET5_0_OR_GREATER + +using System.ComponentModel; + +namespace System.Runtime.CompilerServices +{ + /// + /// Reserved to be used by the compiler for tracking metadata. + /// This class should not be used by developers in source code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + internal static class IsExternalInit + { + } +} + +#endif diff --git a/src/Neo.CLI/CLI/CommandLineOption.cs b/src/Neo.CLI/CLI/CommandLineOption.cs new file mode 100644 index 0000000000..617856cee9 --- /dev/null +++ b/src/Neo.CLI/CLI/CommandLineOption.cs @@ -0,0 +1,36 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// CommandLineOption.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.CLI +{ + public class CommandLineOptions + { + public string? Config { get; init; } + public string? Wallet { get; init; } + public string? Password { get; init; } + public string[]? Plugins { get; set; } + public string? DBEngine { get; init; } + public string? DBPath { get; init; } + public bool? NoVerify { get; init; } + + /// + /// Check if CommandLineOptions was configured + /// + public bool IsValid => + !string.IsNullOrEmpty(Config) || + !string.IsNullOrEmpty(Wallet) || + !string.IsNullOrEmpty(Password) || + !string.IsNullOrEmpty(DBEngine) || + !string.IsNullOrEmpty(DBPath) || + (Plugins?.Length > 0) || + NoVerify is not null; + } +} diff --git a/src/Neo.CLI/CLI/ConsolePercent.cs b/src/Neo.CLI/CLI/ConsolePercent.cs new file mode 100644 index 0000000000..703ca38740 --- /dev/null +++ b/src/Neo.CLI/CLI/ConsolePercent.cs @@ -0,0 +1,146 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ConsolePercent.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; + +namespace Neo.CLI +{ + public class ConsolePercent : IDisposable + { + #region Variables + + private readonly long _maxValue; + private long _value; + private decimal _lastFactor; + private string? _lastPercent; + + private readonly int _x, _y; + + private readonly bool _inputRedirected; + + #endregion + + #region Properties + + /// + /// Value + /// + public long Value + { + get => _value; + set + { + if (value == _value) return; + + _value = Math.Min(value, _maxValue); + Invalidate(); + } + } + + /// + /// Maximum value + /// + public long MaxValue + { + get => _maxValue; + init + { + if (value == _maxValue) return; + + _maxValue = value; + + if (_value > _maxValue) + _value = _maxValue; + + Invalidate(); + } + } + + /// + /// Percent + /// + public decimal Percent + { + get + { + if (_maxValue == 0) return 0; + return (_value * 100M) / _maxValue; + } + } + + #endregion + + /// + /// Constructor + /// + /// Value + /// Maximum value + public ConsolePercent(long value = 0, long maxValue = 100) + { + _inputRedirected = Console.IsInputRedirected; + _lastFactor = -1; + _x = _inputRedirected ? 0 : Console.CursorLeft; + _y = _inputRedirected ? 0 : Console.CursorTop; + + MaxValue = maxValue; + Value = value; + Invalidate(); + } + + /// + /// Invalidate + /// + public void Invalidate() + { + var factor = Math.Round(Percent / 100M, 1); + var percent = Percent.ToString("0.0").PadLeft(5, ' '); + + if (_lastFactor == factor && _lastPercent == percent) + { + return; + } + + _lastFactor = factor; + _lastPercent = percent; + + var fill = string.Empty.PadLeft((int)(10 * factor), '■'); + var clean = string.Empty.PadLeft(10 - fill.Length, _inputRedirected ? '□' : '■'); + + if (_inputRedirected) + { + Console.WriteLine("[" + fill + clean + "] (" + percent + "%)"); + } + else + { + Console.SetCursorPosition(_x, _y); + + var prevColor = Console.ForegroundColor; + + Console.ForegroundColor = ConsoleColor.White; + Console.Write("["); + Console.ForegroundColor = Percent > 50 ? ConsoleColor.Green : ConsoleColor.DarkGreen; + Console.Write(fill); + Console.ForegroundColor = ConsoleColor.White; + Console.Write(clean + "] (" + percent + "%)"); + + Console.ForegroundColor = prevColor; + } + } + + /// + /// Free console + /// + public void Dispose() + { + Console.WriteLine(""); + } + } +} diff --git a/src/Neo.CLI/CLI/Helper.cs b/src/Neo.CLI/CLI/Helper.cs new file mode 100644 index 0000000000..e33f075539 --- /dev/null +++ b/src/Neo.CLI/CLI/Helper.cs @@ -0,0 +1,42 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Helper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.SmartContract.Manifest; +using System; + +namespace Neo.CLI +{ + internal static class Helper + { + public static bool IsYes(this string input) + { + if (input == null) return false; + + input = input.ToLowerInvariant(); + + return input == "yes" || input == "y"; + } + + public static string ToBase64String(this byte[] input) => System.Convert.ToBase64String(input); + + public static void IsScriptValid(this ReadOnlyMemory script, ContractAbi abi) + { + try + { + SmartContract.Helper.Check(script.ToArray(), abi); + } + catch (Exception e) + { + throw new FormatException($"Bad Script or Manifest Format: {e.Message}"); + } + } + } +} diff --git a/src/Neo.CLI/CLI/MainService.Blockchain.cs b/src/Neo.CLI/CLI/MainService.Blockchain.cs new file mode 100644 index 0000000000..090939de49 --- /dev/null +++ b/src/Neo.CLI/CLI/MainService.Blockchain.cs @@ -0,0 +1,318 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// MainService.Blockchain.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.ConsoleService; +using Neo.Network.P2P.Payloads; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using System; +using System.Linq; + +namespace Neo.CLI +{ + partial class MainService + { + /// + /// Process "export blocks" command + /// + /// Start + /// Number of blocks + /// Path + [ConsoleCommand("export blocks", Category = "Blockchain Commands")] + private void OnExportBlocksStartCountCommand(uint start, uint count = uint.MaxValue, string? path = null) + { + uint height = NativeContract.Ledger.CurrentIndex(NeoSystem.StoreView); + if (height < start) + { + ConsoleHelper.Error("invalid start height."); + return; + } + + count = Math.Min(count, height - start + 1); + + if (string.IsNullOrEmpty(path)) + { + path = $"chain.{start}.acc"; + } + + WriteBlocks(start, count, path, true); + } + + [ConsoleCommand("show block", Category = "Blockchain Commands")] + private void OnShowBlockCommand(string indexOrHash) + { + lock (syncRoot) + { + Block? block = null; + + if (uint.TryParse(indexOrHash, out var index)) + block = NativeContract.Ledger.GetBlock(NeoSystem.StoreView, index); + else if (UInt256.TryParse(indexOrHash, out var hash)) + block = NativeContract.Ledger.GetBlock(NeoSystem.StoreView, hash); + else + { + ConsoleHelper.Error("Enter a valid block index or hash."); + return; + } + + if (block is null) + { + ConsoleHelper.Error($"Block {indexOrHash} doesn't exist."); + return; + } + + DateTime blockDatetime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + blockDatetime = blockDatetime.AddMilliseconds(block.Timestamp).ToLocalTime(); + + ConsoleHelper.Info("", "-------------", "Block", "-------------"); + ConsoleHelper.Info(); + ConsoleHelper.Info("", " Timestamp: ", $"{blockDatetime}"); + ConsoleHelper.Info("", " Index: ", $"{block.Index}"); + ConsoleHelper.Info("", " Hash: ", $"{block.Hash}"); + ConsoleHelper.Info("", " Nonce: ", $"{block.Nonce}"); + ConsoleHelper.Info("", " MerkleRoot: ", $"{block.MerkleRoot}"); + ConsoleHelper.Info("", " PrevHash: ", $"{block.PrevHash}"); + ConsoleHelper.Info("", " NextConsensus: ", $"{block.NextConsensus}"); + ConsoleHelper.Info("", " PrimaryIndex: ", $"{block.PrimaryIndex}"); + ConsoleHelper.Info("", " PrimaryPubKey: ", $"{NativeContract.NEO.GetCommittee(NeoSystem.GetSnapshot())[block.PrimaryIndex]}"); + ConsoleHelper.Info("", " Version: ", $"{block.Version}"); + ConsoleHelper.Info("", " Size: ", $"{block.Size} Byte(s)"); + ConsoleHelper.Info(); + + ConsoleHelper.Info("", "-------------", "Witness", "-------------"); + ConsoleHelper.Info(); + ConsoleHelper.Info("", " Invocation Script: ", $"{Convert.ToBase64String(block.Witness.InvocationScript.Span)}"); + ConsoleHelper.Info("", " Verification Script: ", $"{Convert.ToBase64String(block.Witness.VerificationScript.Span)}"); + ConsoleHelper.Info("", " ScriptHash: ", $"{block.Witness.ScriptHash}"); + ConsoleHelper.Info("", " Size: ", $"{block.Witness.Size} Byte(s)"); + ConsoleHelper.Info(); + + ConsoleHelper.Info("", "-------------", "Transactions", "-------------"); + ConsoleHelper.Info(); + + if (block.Transactions.Length == 0) + { + ConsoleHelper.Info("", " No Transaction(s)"); + } + else + { + foreach (var tx in block.Transactions) + ConsoleHelper.Info($" {tx.Hash}"); + } + ConsoleHelper.Info(); + ConsoleHelper.Info("", "--------------------------------------"); + } + } + + [ConsoleCommand("show tx", Category = "Blockchain Commands")] + public void OnShowTransactionCommand(UInt256 hash) + { + lock (syncRoot) + { + var tx = NativeContract.Ledger.GetTransactionState(NeoSystem.StoreView, hash); + + if (tx is null) + { + ConsoleHelper.Error($"Transaction {hash} doesn't exist."); + return; + } + + var block = NativeContract.Ledger.GetHeader(NeoSystem.StoreView, tx.BlockIndex); + + DateTime transactionDatetime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + transactionDatetime = transactionDatetime.AddMilliseconds(block.Timestamp).ToLocalTime(); + + ConsoleHelper.Info("", "-------------", "Transaction", "-------------"); + ConsoleHelper.Info(); + ConsoleHelper.Info("", " Timestamp: ", $"{transactionDatetime}"); + ConsoleHelper.Info("", " Hash: ", $"{tx.Transaction.Hash}"); + ConsoleHelper.Info("", " Nonce: ", $"{tx.Transaction.Nonce}"); + ConsoleHelper.Info("", " Sender: ", $"{tx.Transaction.Sender}"); + ConsoleHelper.Info("", " ValidUntilBlock: ", $"{tx.Transaction.ValidUntilBlock}"); + ConsoleHelper.Info("", " FeePerByte: ", $"{tx.Transaction.FeePerByte} datoshi"); + ConsoleHelper.Info("", " NetworkFee: ", $"{tx.Transaction.NetworkFee} datoshi"); + ConsoleHelper.Info("", " SystemFee: ", $"{tx.Transaction.SystemFee} datoshi"); + ConsoleHelper.Info("", " Script: ", $"{Convert.ToBase64String(tx.Transaction.Script.Span)}"); + ConsoleHelper.Info("", " Version: ", $"{tx.Transaction.Version}"); + ConsoleHelper.Info("", " BlockIndex: ", $"{block.Index}"); + ConsoleHelper.Info("", " BlockHash: ", $"{block.Hash}"); + ConsoleHelper.Info("", " Size: ", $"{tx.Transaction.Size} Byte(s)"); + ConsoleHelper.Info(); + + ConsoleHelper.Info("", "-------------", "Signers", "-------------"); + ConsoleHelper.Info(); + + foreach (var signer in tx.Transaction.Signers) + { + if (signer.Rules.Length == 0) + ConsoleHelper.Info("", " Rules: ", "[]"); + else + ConsoleHelper.Info("", " Rules: ", $"[{string.Join(", ", signer.Rules.Select(s => $"\"{s.ToJson()}\""))}]"); + ConsoleHelper.Info("", " Account: ", $"{signer.Account}"); + ConsoleHelper.Info("", " Scopes: ", $"{signer.Scopes}"); + if (signer.AllowedContracts.Length == 0) + ConsoleHelper.Info("", " AllowedContracts: ", "[]"); + else + ConsoleHelper.Info("", " AllowedContracts: ", $"[{string.Join(", ", signer.AllowedContracts.Select(s => s.ToString()))}]"); + if (signer.AllowedGroups.Length == 0) + ConsoleHelper.Info("", " AllowedGroups: ", "[]"); + else + ConsoleHelper.Info("", " AllowedGroups: ", $"[{string.Join(", ", signer.AllowedGroups.Select(s => s.ToString()))}]"); + ConsoleHelper.Info("", " Size: ", $"{signer.Size} Byte(s)"); + ConsoleHelper.Info(); + } + + ConsoleHelper.Info("", "-------------", "Witnesses", "-------------"); + ConsoleHelper.Info(); + foreach (var witness in tx.Transaction.Witnesses) + { + ConsoleHelper.Info("", " InvocationScript: ", $"{Convert.ToBase64String(witness.InvocationScript.Span)}"); + ConsoleHelper.Info("", " VerificationScript: ", $"{Convert.ToBase64String(witness.VerificationScript.Span)}"); + ConsoleHelper.Info("", " ScriptHash: ", $"{witness.ScriptHash}"); + ConsoleHelper.Info("", " Size: ", $"{witness.Size} Byte(s)"); + ConsoleHelper.Info(); + } + + ConsoleHelper.Info("", "-------------", "Attributes", "-------------"); + ConsoleHelper.Info(); + if (tx.Transaction.Attributes.Length == 0) + { + ConsoleHelper.Info("", " No Attribute(s)."); + } + else + { + foreach (var attribute in tx.Transaction.Attributes) + { + switch (attribute) + { + case Conflicts c: + ConsoleHelper.Info("", " Type: ", $"{c.Type}"); + ConsoleHelper.Info("", " Hash: ", $"{c.Hash}"); + ConsoleHelper.Info("", " Size: ", $"{c.Size} Byte(s)"); + break; + case OracleResponse o: + ConsoleHelper.Info("", " Type: ", $"{o.Type}"); + ConsoleHelper.Info("", " Id: ", $"{o.Id}"); + ConsoleHelper.Info("", " Code: ", $"{o.Code}"); + ConsoleHelper.Info("", " Result: ", $"{Convert.ToBase64String(o.Result.Span)}"); + ConsoleHelper.Info("", " Size: ", $"{o.Size} Byte(s)"); + break; + case HighPriorityAttribute p: + ConsoleHelper.Info("", " Type: ", $"{p.Type}"); + break; + case NotValidBefore n: + ConsoleHelper.Info("", " Type: ", $"{n.Type}"); + ConsoleHelper.Info("", " Height: ", $"{n.Height}"); + break; + default: + ConsoleHelper.Info("", " Type: ", $"{attribute.Type}"); + ConsoleHelper.Info("", " Size: ", $"{attribute.Size} Byte(s)"); + break; + } + } + } + ConsoleHelper.Info(); + ConsoleHelper.Info("", "--------------------------------------"); + } + } + + [ConsoleCommand("show contract", Category = "Blockchain Commands")] + public void OnShowContractCommand(string nameOrHash) + { + lock (syncRoot) + { + ContractState? contract = null; + + if (UInt160.TryParse(nameOrHash, out var scriptHash)) + contract = NativeContract.ContractManagement.GetContract(NeoSystem.StoreView, scriptHash); + else + { + var nativeContract = NativeContract.Contracts.SingleOrDefault(s => s.Name.Equals(nameOrHash, StringComparison.InvariantCultureIgnoreCase)); + + if (nativeContract != null) + contract = NativeContract.ContractManagement.GetContract(NeoSystem.StoreView, nativeContract.Hash); + } + + if (contract is null) + { + ConsoleHelper.Error($"Contract {nameOrHash} doesn't exist."); + return; + } + + ConsoleHelper.Info("", "-------------", "Contract", "-------------"); + ConsoleHelper.Info(); + ConsoleHelper.Info("", " Name: ", $"{contract.Manifest.Name}"); + ConsoleHelper.Info("", " Hash: ", $"{contract.Hash}"); + ConsoleHelper.Info("", " Id: ", $"{contract.Id}"); + ConsoleHelper.Info("", " UpdateCounter: ", $"{contract.UpdateCounter}"); + ConsoleHelper.Info("", " SupportedStandards: ", $"{string.Join(" ", contract.Manifest.SupportedStandards)}"); + ConsoleHelper.Info("", " Checksum: ", $"{contract.Nef.CheckSum}"); + ConsoleHelper.Info("", " Compiler: ", $"{contract.Nef.Compiler}"); + ConsoleHelper.Info("", " SourceCode: ", $"{contract.Nef.Source}"); + ConsoleHelper.Info("", " Trusts: ", $"[{string.Join(", ", contract.Manifest.Trusts.Select(s => s.ToJson()?.GetString()))}]"); + if (contract.Manifest.Extra is not null) + { + foreach (var extra in contract.Manifest.Extra.Properties) + { + ConsoleHelper.Info("", $" {extra.Key,18}: ", $"{extra.Value?.GetString()}"); + } + } + ConsoleHelper.Info(); + + ConsoleHelper.Info("", "-------------", "Groups", "-------------"); + ConsoleHelper.Info(); + if (contract.Manifest.Groups.Length == 0) + { + ConsoleHelper.Info("", " No Group(s)."); + } + else + { + foreach (var group in contract.Manifest.Groups) + { + ConsoleHelper.Info("", " PubKey: ", $"{group.PubKey}"); + ConsoleHelper.Info("", " Signature: ", $"{Convert.ToBase64String(group.Signature)}"); + } + } + ConsoleHelper.Info(); + + ConsoleHelper.Info("", "-------------", "Permissions", "-------------"); + ConsoleHelper.Info(); + foreach (var permission in contract.Manifest.Permissions) + { + ConsoleHelper.Info("", " Contract: ", $"{permission.Contract.ToJson()?.GetString()}"); + if (permission.Methods.IsWildcard) + ConsoleHelper.Info("", " Methods: ", "*"); + else + ConsoleHelper.Info("", " Methods: ", $"{string.Join(", ", permission.Methods)}"); + ConsoleHelper.Info(); + } + + ConsoleHelper.Info("", "-------------", "Methods", "-------------"); + ConsoleHelper.Info(); + foreach (var method in contract.Manifest.Abi.Methods) + { + ConsoleHelper.Info("", " Name: ", $"{method.Name}"); + ConsoleHelper.Info("", " Safe: ", $"{method.Safe}"); + ConsoleHelper.Info("", " Offset: ", $"{method.Offset}"); + ConsoleHelper.Info("", " Parameters: ", $"[{string.Join(", ", method.Parameters.Select(s => s.Type.ToString()))}]"); + ConsoleHelper.Info("", " ReturnType: ", $"{method.ReturnType}"); + ConsoleHelper.Info(); + } + + ConsoleHelper.Info("", "-------------", "Script", "-------------"); + ConsoleHelper.Info(); + ConsoleHelper.Info($" {Convert.ToBase64String(contract.Nef.Script.Span)}"); + ConsoleHelper.Info(); + ConsoleHelper.Info("", "--------------------------------"); + } + } + } +} diff --git a/src/Neo.CLI/CLI/MainService.CommandLine.cs b/src/Neo.CLI/CLI/MainService.CommandLine.cs new file mode 100644 index 0000000000..074f252f35 --- /dev/null +++ b/src/Neo.CLI/CLI/MainService.CommandLine.cs @@ -0,0 +1,93 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// MainService.CommandLine.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.Extensions.Configuration; +using System.CommandLine; +using System.CommandLine.Invocation; +using System.CommandLine.NamingConventionBinder; +using System.Reflection; + +namespace Neo.CLI +{ + public partial class MainService + { + public int OnStartWithCommandLine(string[] args) + { + RootCommand rootCommand = new(Assembly.GetExecutingAssembly().GetCustomAttribute()!.Title) + { + new Option(new[] { "-c", "--config","/config" }, "Specifies the config file."), + new Option(new[] { "-w", "--wallet","/wallet" }, "The path of the neo3 wallet [*.json]."), + new Option(new[] { "-p", "--password" ,"/password" }, "Password to decrypt the wallet, either from the command line or config file."), + new Option(new[] { "--db-engine","/db-engine" }, "Specify the db engine."), + new Option(new[] { "--db-path","/db-path" }, "Specify the db path."), + new Option(new[] { "--noverify","/noverify" }, "Indicates whether the blocks need to be verified when importing."), + new Option(new[] { "--plugins","/plugins" }, "The list of plugins, if not present, will be installed [plugin1 plugin2]."), + }; + + rootCommand.Handler = CommandHandler.Create(Handle); + return rootCommand.Invoke(args); + } + + private void Handle(RootCommand command, CommandLineOptions options, InvocationContext context) + { + Start(options); + } + + private static void CustomProtocolSettings(CommandLineOptions options, ProtocolSettings settings) + { + var tempSetting = settings; + // if specified config, then load the config and check the network + if (!string.IsNullOrEmpty(options.Config)) + { + tempSetting = ProtocolSettings.Load(options.Config); + } + + var customSetting = new ProtocolSettings + { + Network = tempSetting.Network, + AddressVersion = tempSetting.AddressVersion, + StandbyCommittee = tempSetting.StandbyCommittee, + ValidatorsCount = tempSetting.ValidatorsCount, + SeedList = tempSetting.SeedList, + MillisecondsPerBlock = tempSetting.MillisecondsPerBlock, + MaxTransactionsPerBlock = tempSetting.MaxTransactionsPerBlock, + MemoryPoolMaxTransactions = tempSetting.MemoryPoolMaxTransactions, + MaxTraceableBlocks = tempSetting.MaxTraceableBlocks, + InitialGasDistribution = tempSetting.InitialGasDistribution, + Hardforks = tempSetting.Hardforks + }; + + if (!string.IsNullOrEmpty(options.Config)) ProtocolSettings.Custom = customSetting; + } + + private static void CustomApplicationSettings(CommandLineOptions options, Settings settings) + { + var tempSetting = string.IsNullOrEmpty(options.Config) ? settings : new Settings(new ConfigurationBuilder().AddJsonFile(options.Config, optional: true).Build().GetSection("ApplicationConfiguration")); + var customSetting = new Settings + { + Logger = tempSetting.Logger, + Storage = new StorageSettings + { + Engine = options.DBEngine ?? tempSetting.Storage.Engine, + Path = options.DBPath ?? tempSetting.Storage.Path + }, + P2P = tempSetting.P2P, + UnlockWallet = new UnlockWalletSettings + { + Path = options.Wallet ?? tempSetting.UnlockWallet.Path, + Password = options.Password ?? tempSetting.UnlockWallet.Password + }, + Contracts = tempSetting.Contracts + }; + if (options.IsValid) Settings.Custom = customSetting; + } + } +} diff --git a/src/Neo.CLI/CLI/MainService.Contracts.cs b/src/Neo.CLI/CLI/MainService.Contracts.cs new file mode 100644 index 0000000000..684b3330bc --- /dev/null +++ b/src/Neo.CLI/CLI/MainService.Contracts.cs @@ -0,0 +1,190 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// MainService.Contracts.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.ConsoleService; +using Neo.Json; +using Neo.Network.P2P.Payloads; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using System; +using System.Linq; +using System.Numerics; + +namespace Neo.CLI +{ + partial class MainService + { + /// + /// Process "deploy" command + /// + /// File path + /// Manifest path + /// Extra data for deploy + [ConsoleCommand("deploy", Category = "Contract Commands")] + private void OnDeployCommand(string filePath, string? manifestPath = null, JObject? data = null) + { + if (NoWallet()) return; + byte[] script = LoadDeploymentScript(filePath, manifestPath, data, out var nef, out var manifest); + Transaction tx; + try + { + tx = CurrentWallet!.MakeTransaction(NeoSystem.StoreView, script); + } + catch (InvalidOperationException e) + { + ConsoleHelper.Error(GetExceptionMessage(e)); + return; + } + UInt160 hash = SmartContract.Helper.GetContractHash(tx.Sender, nef.CheckSum, manifest.Name); + + ConsoleHelper.Info("Contract hash: ", $"{hash}"); + ConsoleHelper.Info("Gas consumed: ", $"{new BigDecimal((BigInteger)tx.SystemFee, NativeContract.GAS.Decimals)} GAS"); + ConsoleHelper.Info("Network fee: ", $"{new BigDecimal((BigInteger)tx.NetworkFee, NativeContract.GAS.Decimals)} GAS"); + ConsoleHelper.Info("Total fee: ", $"{new BigDecimal((BigInteger)(tx.SystemFee + tx.NetworkFee), NativeContract.GAS.Decimals)} GAS"); + if (!ConsoleHelper.ReadUserInput("Relay tx? (no|yes)").IsYes()) // Add this in case just want to get hash but not relay + { + return; + } + SignAndSendTx(NeoSystem.StoreView, tx); + } + + /// + /// Process "update" command + /// + /// Script hash + /// File path + /// Manifest path + /// Sender + /// Signer Accounts + /// Extra data for update + [ConsoleCommand("update", Category = "Contract Commands")] + private void OnUpdateCommand(UInt160 scriptHash, string filePath, string manifestPath, UInt160 sender, UInt160[]? signerAccounts = null, JObject? data = null) + { + Signer[] signers = Array.Empty(); + + if (NoWallet()) return; + if (sender != null) + { + if (signerAccounts == null) + signerAccounts = new[] { sender }; + else if (signerAccounts.Contains(sender) && signerAccounts[0] != sender) + { + var signersList = signerAccounts.ToList(); + signersList.Remove(sender); + signerAccounts = signersList.Prepend(sender).ToArray(); + } + else if (!signerAccounts.Contains(sender)) + { + signerAccounts = signerAccounts.Prepend(sender).ToArray(); + } + signers = signerAccounts.Select(p => new Signer() { Account = p, Scopes = WitnessScope.CalledByEntry }).ToArray(); + } + + Transaction tx; + try + { + byte[] script = LoadUpdateScript(scriptHash, filePath, manifestPath, data, out var nef, out var manifest); + tx = CurrentWallet!.MakeTransaction(NeoSystem.StoreView, script, sender, signers); + } + catch (InvalidOperationException e) + { + ConsoleHelper.Error(GetExceptionMessage(e)); + return; + } + ContractState contract = NativeContract.ContractManagement.GetContract(NeoSystem.StoreView, scriptHash); + if (contract == null) + { + ConsoleHelper.Warning($"Can't upgrade, contract hash not exist: {scriptHash}"); + } + else + { + ConsoleHelper.Info("Contract hash: ", $"{scriptHash}"); + ConsoleHelper.Info("Updated times: ", $"{contract.UpdateCounter}"); + ConsoleHelper.Info("Gas consumed: ", $"{new BigDecimal((BigInteger)tx.SystemFee, NativeContract.GAS.Decimals)} GAS"); + ConsoleHelper.Info("Network fee: ", $"{new BigDecimal((BigInteger)tx.NetworkFee, NativeContract.GAS.Decimals)} GAS"); + ConsoleHelper.Info("Total fee: ", $"{new BigDecimal((BigInteger)(tx.SystemFee + tx.NetworkFee), NativeContract.GAS.Decimals)} GAS"); + if (!ConsoleHelper.ReadUserInput("Relay tx? (no|yes)").IsYes()) // Add this in case just want to get hash but not relay + { + return; + } + SignAndSendTx(NeoSystem.StoreView, tx); + } + } + + /// + /// Process "invoke" command + /// + /// Script hash + /// Operation + /// Contract parameters + /// Transaction's sender + /// Signer's accounts + /// Max fee for running the script, in the unit of GAS + [ConsoleCommand("invoke", Category = "Contract Commands")] + private void OnInvokeCommand(UInt160 scriptHash, string operation, JArray? contractParameters = null, UInt160? sender = null, UInt160[]? signerAccounts = null, decimal maxGas = 20) + { + // In the unit of datoshi, 1 datoshi = 1e-8 GAS + var datoshi = new BigDecimal(maxGas, NativeContract.GAS.Decimals); + Signer[] signers = Array.Empty(); + if (!NoWallet()) + { + if (sender == null) + sender = CurrentWallet!.GetDefaultAccount()?.ScriptHash; + + if (sender != null) + { + if (signerAccounts == null) + signerAccounts = new UInt160[1] { sender }; + else if (signerAccounts.Contains(sender) && signerAccounts[0] != sender) + { + var signersList = signerAccounts.ToList(); + signersList.Remove(sender); + signerAccounts = signersList.Prepend(sender).ToArray(); + } + else if (!signerAccounts.Contains(sender)) + { + signerAccounts = signerAccounts.Prepend(sender).ToArray(); + } + signers = signerAccounts.Select(p => new Signer() { Account = p, Scopes = WitnessScope.CalledByEntry }).ToArray(); + } + } + + Transaction tx = new Transaction + { + Signers = signers, + Attributes = Array.Empty(), + Witnesses = Array.Empty(), + }; + + if (!OnInvokeWithResult(scriptHash, operation, out _, tx, contractParameters, datoshi: (long)datoshi.Value)) return; + + if (NoWallet()) return; + try + { + tx = CurrentWallet!.MakeTransaction(NeoSystem.StoreView, tx.Script, sender, signers, maxGas: (long)datoshi.Value); + } + catch (InvalidOperationException e) + { + ConsoleHelper.Error(GetExceptionMessage(e)); + return; + } + ConsoleHelper.Info("Network fee: ", + $"{new BigDecimal((BigInteger)tx.NetworkFee, NativeContract.GAS.Decimals)} GAS\t", + "Total fee: ", + $"{new BigDecimal((BigInteger)(tx.SystemFee + tx.NetworkFee), NativeContract.GAS.Decimals)} GAS"); + if (!ConsoleHelper.ReadUserInput("Relay tx? (no|yes)").IsYes()) + { + return; + } + SignAndSendTx(NeoSystem.StoreView, tx); + } + } +} diff --git a/src/Neo.CLI/CLI/MainService.Logger.cs b/src/Neo.CLI/CLI/MainService.Logger.cs new file mode 100644 index 0000000000..80624779c7 --- /dev/null +++ b/src/Neo.CLI/CLI/MainService.Logger.cs @@ -0,0 +1,176 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// MainService.Logger.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.ConsoleService; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using static System.IO.Path; + +namespace Neo.CLI +{ + partial class MainService + { + private static readonly ConsoleColorSet DebugColor = new(ConsoleColor.Cyan); + private static readonly ConsoleColorSet InfoColor = new(ConsoleColor.White); + private static readonly ConsoleColorSet WarningColor = new(ConsoleColor.Yellow); + private static readonly ConsoleColorSet ErrorColor = new(ConsoleColor.Red); + private static readonly ConsoleColorSet FatalColor = new(ConsoleColor.Red); + + private readonly object syncRoot = new(); + private bool _showLog = Settings.Default.Logger.ConsoleOutput; + + private void Initialize_Logger() + { + Utility.Logging += OnLog; + } + + private void Dispose_Logger() + { + Utility.Logging -= OnLog; + } + + /// + /// Process "console log off" command to turn off console log + /// + [ConsoleCommand("console log off", Category = "Log Commands")] + private void OnLogOffCommand() + { + _showLog = false; + } + + /// + /// Process "console log on" command to turn on the console log + /// + [ConsoleCommand("console log on", Category = "Log Commands")] + private void OnLogOnCommand() + { + _showLog = true; + } + + private static void GetErrorLogs(StringBuilder sb, Exception ex) + { + sb.AppendLine(ex.GetType().ToString()); + sb.AppendLine(ex.Message); + sb.AppendLine(ex.StackTrace); + if (ex is AggregateException ex2) + { + foreach (Exception inner in ex2.InnerExceptions) + { + sb.AppendLine(); + GetErrorLogs(sb, inner); + } + } + else if (ex.InnerException != null) + { + sb.AppendLine(); + GetErrorLogs(sb, ex.InnerException); + } + } + + private void OnLog(string source, LogLevel level, object message) + { + if (!Settings.Default.Logger.Active) + return; + + if (message is Exception ex) + { + var sb = new StringBuilder(); + GetErrorLogs(sb, ex); + message = sb.ToString(); + } + + lock (syncRoot) + { + DateTime now = DateTime.Now; + var log = $"[{now.TimeOfDay:hh\\:mm\\:ss\\.fff}]"; + if (_showLog) + { + var currentColor = new ConsoleColorSet(); + var messages = message is string msg ? Parse(msg) : new[] { message.ToString() }; + ConsoleColorSet logColor; + string logLevel; + switch (level) + { + case LogLevel.Debug: logColor = DebugColor; logLevel = "DEBUG"; break; + case LogLevel.Error: logColor = ErrorColor; logLevel = "ERROR"; break; + case LogLevel.Fatal: logColor = FatalColor; logLevel = "FATAL"; break; + case LogLevel.Info: logColor = InfoColor; logLevel = "INFO"; break; + case LogLevel.Warning: logColor = WarningColor; logLevel = "WARN"; break; + default: logColor = InfoColor; logLevel = "INFO"; break; + } + logColor.Apply(); + Console.Write($"{logLevel} {log} \t{messages[0],-20}"); + for (var i = 1; i < messages.Length; i++) + { + if (messages[i]?.Length > 20) + { + messages[i] = $"{messages[i]![..10]}...{messages[i]![(messages[i]!.Length - 10)..]}"; + } + Console.Write(i % 2 == 0 ? $"={messages[i]} " : $" {messages[i]}"); + } + currentColor.Apply(); + Console.WriteLine(); + } + + if (string.IsNullOrEmpty(Settings.Default.Logger.Path)) return; + var sb = new StringBuilder(source); + foreach (var c in GetInvalidFileNameChars()) + sb.Replace(c, '-'); + var path = Combine(Settings.Default.Logger.Path, sb.ToString()); + Directory.CreateDirectory(path); + path = Combine(path, $"{now:yyyy-MM-dd}.log"); + try + { + File.AppendAllLines(path, new[] { $"[{level}]{log} {message}" }); + } + catch (IOException) + { + Console.WriteLine("Error writing the log file: " + path); + } + } + } + + /// + /// Parse the log message + /// + /// expected format [key1 = msg1 key2 = msg2] + /// + private static string[] Parse(string message) + { + var equals = message.Trim().Split('='); + + if (equals.Length == 1) return new[] { message }; + + var messages = new List(); + foreach (var t in @equals) + { + var msg = t.Trim(); + var parts = msg.Split(' '); + var d = parts.Take(parts.Length - 1); + + if (parts.Length > 1) + { + messages.Add(string.Join(" ", d)); + } + var last = parts.LastOrDefault(); + if (last is not null) + { + messages.Add(last); + } + } + + return messages.ToArray(); + } + } +} diff --git a/src/Neo.CLI/CLI/MainService.NEP17.cs b/src/Neo.CLI/CLI/MainService.NEP17.cs new file mode 100644 index 0000000000..131d171dc6 --- /dev/null +++ b/src/Neo.CLI/CLI/MainService.NEP17.cs @@ -0,0 +1,140 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// MainService.NEP17.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.ConsoleService; +using Neo.Json; +using Neo.Network.P2P.Payloads; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM.Types; +using Neo.Wallets; +using System; +using System.Linq; +using Array = System.Array; + +namespace Neo.CLI +{ + partial class MainService + { + /// + /// Process "transfer" command + /// + /// Script hash + /// To + /// Amount + /// From + /// Data + /// Signer's accounts + [ConsoleCommand("transfer", Category = "NEP17 Commands")] + private void OnTransferCommand(UInt160 tokenHash, UInt160 to, decimal amount, UInt160? from = null, string? data = null, UInt160[]? signersAccounts = null) + { + var snapshot = NeoSystem.StoreView; + var asset = new AssetDescriptor(snapshot, NeoSystem.Settings, tokenHash); + var value = new BigDecimal(amount, asset.Decimals); + + if (NoWallet()) return; + + Transaction tx; + try + { + tx = CurrentWallet!.MakeTransaction(snapshot, new[] + { + new TransferOutput + { + AssetId = tokenHash, + Value = value, + ScriptHash = to, + Data = data + } + }, from: from, cosigners: signersAccounts?.Select(p => new Signer + { + // default access for transfers should be valid only for first invocation + Scopes = WitnessScope.CalledByEntry, + Account = p + }) + .ToArray() ?? Array.Empty()); + } + catch (InvalidOperationException e) + { + ConsoleHelper.Error(GetExceptionMessage(e)); + return; + } + if (!ConsoleHelper.ReadUserInput("Relay tx(no|yes)").IsYes()) + { + return; + } + SignAndSendTx(snapshot, tx); + } + + /// + /// Process "balanceOf" command + /// + /// Script hash + /// Address + [ConsoleCommand("balanceOf", Category = "NEP17 Commands")] + private void OnBalanceOfCommand(UInt160 tokenHash, UInt160 address) + { + var arg = new JObject + { + ["type"] = "Hash160", + ["value"] = address.ToString() + }; + + var asset = new AssetDescriptor(NeoSystem.StoreView, NeoSystem.Settings, tokenHash); + + if (!OnInvokeWithResult(tokenHash, "balanceOf", out StackItem balanceResult, null, new JArray(arg))) return; + + var balance = new BigDecimal(((PrimitiveType)balanceResult).GetInteger(), asset.Decimals); + + Console.WriteLine(); + ConsoleHelper.Info($"{asset.AssetName} balance: ", $"{balance}"); + } + + /// + /// Process "name" command + /// + /// Script hash + [ConsoleCommand("name", Category = "NEP17 Commands")] + private void OnNameCommand(UInt160 tokenHash) + { + ContractState contract = NativeContract.ContractManagement.GetContract(NeoSystem.StoreView, tokenHash); + if (contract == null) Console.WriteLine($"Contract hash not exist: {tokenHash}"); + else ConsoleHelper.Info("Result: ", contract.Manifest.Name); + } + + /// + /// Process "decimals" command + /// + /// Script hash + [ConsoleCommand("decimals", Category = "NEP17 Commands")] + private void OnDecimalsCommand(UInt160 tokenHash) + { + if (!OnInvokeWithResult(tokenHash, "decimals", out StackItem result)) return; + + ConsoleHelper.Info("Result: ", $"{((PrimitiveType)result).GetInteger()}"); + } + + /// + /// Process "totalSupply" command + /// + /// Script hash + [ConsoleCommand("totalSupply", Category = "NEP17 Commands")] + private void OnTotalSupplyCommand(UInt160 tokenHash) + { + if (!OnInvokeWithResult(tokenHash, "totalSupply", out StackItem result)) return; + + var asset = new AssetDescriptor(NeoSystem.StoreView, NeoSystem.Settings, tokenHash); + var totalSupply = new BigDecimal(((PrimitiveType)result).GetInteger(), asset.Decimals); + + ConsoleHelper.Info("Result: ", $"{totalSupply}"); + } + } +} diff --git a/src/Neo.CLI/CLI/MainService.Native.cs b/src/Neo.CLI/CLI/MainService.Native.cs new file mode 100644 index 0000000000..b7562f3368 --- /dev/null +++ b/src/Neo.CLI/CLI/MainService.Native.cs @@ -0,0 +1,29 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// MainService.Native.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.ConsoleService; +using Neo.SmartContract.Native; +using System.Linq; + +namespace Neo.CLI +{ + partial class MainService + { + /// + /// Process "list nativecontract" command + /// + [ConsoleCommand("list nativecontract", Category = "Native Contract")] + private void OnListNativeContract() + { + NativeContract.Contracts.ToList().ForEach(p => ConsoleHelper.Info($"\t{p.Name,-20}", $"{p.Hash}")); + } + } +} diff --git a/src/Neo.CLI/CLI/MainService.Network.cs b/src/Neo.CLI/CLI/MainService.Network.cs new file mode 100644 index 0000000000..38d6fd1d28 --- /dev/null +++ b/src/Neo.CLI/CLI/MainService.Network.cs @@ -0,0 +1,165 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// MainService.Network.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Akka.Actor; +using Neo.ConsoleService; +using Neo.IO; +using Neo.Json; +using Neo.Network.P2P; +using Neo.Network.P2P.Capabilities; +using Neo.Network.P2P.Payloads; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using System; +using System.Net; + +namespace Neo.CLI +{ + partial class MainService + { + /// + /// Process "broadcast addr" command + /// + /// Payload + /// Port + [ConsoleCommand("broadcast addr", Category = "Network Commands")] + private void OnBroadcastAddressCommand(IPAddress payload, ushort port) + { + if (payload == null) + { + ConsoleHelper.Warning("You must input the payload to relay."); + return; + } + + OnBroadcastCommand(MessageCommand.Addr, + AddrPayload.Create( + NetworkAddressWithTime.Create( + payload, DateTime.UtcNow.ToTimestamp(), + new FullNodeCapability(), + new ServerCapability(NodeCapabilityType.TcpServer, port)) + )); + } + + /// + /// Process "broadcast block" command + /// + /// Hash + [ConsoleCommand("broadcast block", Category = "Network Commands")] + private void OnBroadcastGetBlocksByHashCommand(UInt256 hash) + { + OnBroadcastCommand(MessageCommand.Block, NativeContract.Ledger.GetBlock(NeoSystem.StoreView, hash)); + } + + /// + /// Process "broadcast block" command + /// + /// Block index + [ConsoleCommand("broadcast block", Category = "Network Commands")] + private void OnBroadcastGetBlocksByHeightCommand(uint height) + { + OnBroadcastCommand(MessageCommand.Block, NativeContract.Ledger.GetBlock(NeoSystem.StoreView, height)); + } + + /// + /// Process "broadcast getblocks" command + /// + /// Hash + [ConsoleCommand("broadcast getblocks", Category = "Network Commands")] + private void OnBroadcastGetBlocksCommand(UInt256 hash) + { + OnBroadcastCommand(MessageCommand.GetBlocks, GetBlocksPayload.Create(hash)); + } + + /// + /// Process "broadcast getheaders" command + /// + /// Index + [ConsoleCommand("broadcast getheaders", Category = "Network Commands")] + private void OnBroadcastGetHeadersCommand(uint index) + { + OnBroadcastCommand(MessageCommand.GetHeaders, GetBlockByIndexPayload.Create(index)); + } + + /// + /// Process "broadcast getdata" command + /// + /// Type + /// Payload + [ConsoleCommand("broadcast getdata", Category = "Network Commands")] + private void OnBroadcastGetDataCommand(InventoryType type, UInt256[] payload) + { + OnBroadcastCommand(MessageCommand.GetData, InvPayload.Create(type, payload)); + } + + /// + /// Process "broadcast inv" command + /// + /// Type + /// Payload + [ConsoleCommand("broadcast inv", Category = "Network Commands")] + private void OnBroadcastInvCommand(InventoryType type, UInt256[] payload) + { + OnBroadcastCommand(MessageCommand.Inv, InvPayload.Create(type, payload)); + } + + /// + /// Process "broadcast transaction" command + /// + /// Hash + [ConsoleCommand("broadcast transaction", Category = "Network Commands")] + private void OnBroadcastTransactionCommand(UInt256 hash) + { + if (NeoSystem.MemPool.TryGetValue(hash, out Transaction tx)) + OnBroadcastCommand(MessageCommand.Transaction, tx); + } + + private void OnBroadcastCommand(MessageCommand command, ISerializable ret) + { + NeoSystem.LocalNode.Tell(Message.Create(command, ret)); + } + + /// + /// Process "relay" command + /// + /// Json object + [ConsoleCommand("relay", Category = "Network Commands")] + private void OnRelayCommand(JObject jsonObjectToRelay) + { + if (jsonObjectToRelay == null) + { + ConsoleHelper.Warning("You must input JSON object to relay."); + return; + } + + try + { + ContractParametersContext context = ContractParametersContext.Parse(jsonObjectToRelay.ToString(), NeoSystem.StoreView); + if (!context.Completed) + { + ConsoleHelper.Error("The signature is incomplete."); + return; + } + if (!(context.Verifiable is Transaction tx)) + { + ConsoleHelper.Warning("Only support to relay transaction."); + return; + } + tx.Witnesses = context.GetWitnesses(); + NeoSystem.Blockchain.Tell(tx); + Console.WriteLine($"Data relay success, the hash is shown as follows: {Environment.NewLine}{tx.Hash}"); + } + catch (Exception e) + { + ConsoleHelper.Error(GetExceptionMessage(e)); + } + } + } +} diff --git a/src/Neo.CLI/CLI/MainService.Node.cs b/src/Neo.CLI/CLI/MainService.Node.cs new file mode 100644 index 0000000000..5dd16f53e8 --- /dev/null +++ b/src/Neo.CLI/CLI/MainService.Node.cs @@ -0,0 +1,119 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// MainService.Node.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Akka.Actor; +using Neo.ConsoleService; +using Neo.Network.P2P; +using Neo.Network.P2P.Payloads; +using Neo.SmartContract.Native; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Neo.CLI +{ + partial class MainService + { + /// + /// Process "show pool" command + /// + [ConsoleCommand("show pool", Category = "Node Commands", Description = "Show the current state of the mempool")] + private void OnShowPoolCommand(bool verbose = false) + { + int verifiedCount, unverifiedCount; + if (verbose) + { + NeoSystem.MemPool.GetVerifiedAndUnverifiedTransactions( + out IEnumerable verifiedTransactions, + out IEnumerable unverifiedTransactions); + ConsoleHelper.Info("Verified Transactions:"); + foreach (Transaction tx in verifiedTransactions) + Console.WriteLine($" {tx.Hash} {tx.GetType().Name} {tx.NetworkFee} GAS_NetFee"); + ConsoleHelper.Info("Unverified Transactions:"); + foreach (Transaction tx in unverifiedTransactions) + Console.WriteLine($" {tx.Hash} {tx.GetType().Name} {tx.NetworkFee} GAS_NetFee"); + + verifiedCount = verifiedTransactions.Count(); + unverifiedCount = unverifiedTransactions.Count(); + } + else + { + verifiedCount = NeoSystem.MemPool.VerifiedCount; + unverifiedCount = NeoSystem.MemPool.UnVerifiedCount; + } + Console.WriteLine($"total: {NeoSystem.MemPool.Count}, verified: {verifiedCount}, unverified: {unverifiedCount}"); + } + + /// + /// Process "show state" command + /// + [ConsoleCommand("show state", Category = "Node Commands", Description = "Show the current state of the node")] + private void OnShowStateCommand() + { + var cancel = new CancellationTokenSource(); + + Console.CursorVisible = false; + Console.Clear(); + + Task broadcast = Task.Run(async () => + { + while (!cancel.Token.IsCancellationRequested) + { + NeoSystem.LocalNode.Tell(Message.Create(MessageCommand.Ping, PingPayload.Create(NativeContract.Ledger.CurrentIndex(NeoSystem.StoreView)))); + await Task.Delay(NeoSystem.Settings.TimePerBlock, cancel.Token); + } + }); + Task task = Task.Run(async () => + { + int maxLines = 0; + while (!cancel.Token.IsCancellationRequested) + { + uint height = NativeContract.Ledger.CurrentIndex(NeoSystem.StoreView); + uint headerHeight = NeoSystem.HeaderCache.Last?.Index ?? height; + + Console.SetCursorPosition(0, 0); + WriteLineWithoutFlicker($"block: {height}/{headerHeight} connected: {LocalNode.ConnectedCount} unconnected: {LocalNode.UnconnectedCount}", Console.WindowWidth - 1); + + int linesWritten = 1; + foreach (RemoteNode node in LocalNode.GetRemoteNodes().OrderByDescending(u => u.LastBlockIndex).Take(Console.WindowHeight - 2).ToArray()) + { + ConsoleHelper.Info(" ip: ", + $"{node.Remote.Address,-15}\t", + "port: ", + $"{node.Remote.Port,-5}\t", + "listen: ", + $"{node.ListenerTcpPort,-5}\t", + "height: ", + $"{node.LastBlockIndex,-7}"); + linesWritten++; + } + + maxLines = Math.Max(maxLines, linesWritten); + + while (linesWritten < maxLines) + { + WriteLineWithoutFlicker("", Console.WindowWidth - 1); + maxLines--; + } + + await Task.Delay(500, cancel.Token); + } + }); + ReadLine(); + cancel.Cancel(); + try { Task.WaitAll(task, broadcast); } catch { } + Console.WriteLine(); + Console.CursorVisible = true; + } + } +} diff --git a/src/Neo.CLI/CLI/MainService.Plugins.cs b/src/Neo.CLI/CLI/MainService.Plugins.cs new file mode 100644 index 0000000000..761dd386d2 --- /dev/null +++ b/src/Neo.CLI/CLI/MainService.Plugins.cs @@ -0,0 +1,281 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// MainService.Plugins.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Akka.Util.Internal; +using Microsoft.Extensions.Configuration; +using Neo.ConsoleService; +using Neo.Plugins; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Json; +using System.Reflection; +using System.Text.Json.Nodes; +using System.Threading.Tasks; + +namespace Neo.CLI +{ + partial class MainService + { + /// + /// Process "install" command + /// + /// Plugin name + /// Custom plugins download url, this is optional. + [ConsoleCommand("install", Category = "Plugin Commands")] + private void OnInstallCommand(string pluginName, string? downloadUrl = null) + { + if (PluginExists(pluginName)) + { + ConsoleHelper.Warning($"Plugin already exist."); + return; + } + + var result = InstallPluginAsync(pluginName, downloadUrl).GetAwaiter().GetResult(); + if (result) + { + var asmName = Assembly.GetExecutingAssembly().GetName().Name; + ConsoleHelper.Info("", $"Install successful, please restart \"{asmName}\"."); + } + } + + /// + /// Force to install a plugin again. This will overwrite + /// existing plugin files, in case of any file missing or + /// damage to the old version. + /// + /// name of the plugin + [ConsoleCommand("reinstall", Category = "Plugin Commands", Description = "Overwrite existing plugin by force.")] + private void OnReinstallCommand(string pluginName) + { + var result = InstallPluginAsync(pluginName, overWrite: true).GetAwaiter().GetResult(); + if (result) + { + var asmName = Assembly.GetExecutingAssembly().GetName().Name; + ConsoleHelper.Info("", $"Reinstall successful, please restart \"{asmName}\"."); + } + } + + /// + /// Download plugin from github release + /// The function of download and install are divided + /// for the consideration of `update` command that + /// might be added in the future. + /// + /// name of the plugin + /// + /// Custom plugin download url. + /// + /// Downloaded content + private static async Task DownloadPluginAsync(string pluginName, Version pluginVersion, string? customDownloadUrl = null, bool prerelease = false) + { + using var httpClient = new HttpClient(); + + var asmName = Assembly.GetExecutingAssembly().GetName(); + httpClient.DefaultRequestHeaders.UserAgent.Add(new(asmName.Name!, asmName.Version!.ToString(3))); + var url = customDownloadUrl == null ? Settings.Default.Plugins.DownloadUrl : new Uri(customDownloadUrl); + var json = await httpClient.GetFromJsonAsync(url) ?? throw new HttpRequestException($"Failed: {url}"); + var jsonRelease = json.AsArray() + .SingleOrDefault(s => + s != null && + s["tag_name"]!.GetValue() == $"v{pluginVersion.ToString(3)}" && + s["prerelease"]!.GetValue() == prerelease) ?? throw new Exception($"Could not find Release {pluginVersion}"); + + var jsonAssets = jsonRelease + .AsObject() + .SingleOrDefault(s => s.Key == "assets").Value ?? throw new Exception("Could not find any Plugins"); + + var jsonPlugin = jsonAssets + .AsArray() + .SingleOrDefault(s => + Path.GetFileNameWithoutExtension( + s!["name"]!.GetValue()).Equals(pluginName, StringComparison.InvariantCultureIgnoreCase)) + ?? throw new Exception($"Could not find {pluginName}"); + + var downloadUrl = jsonPlugin["browser_download_url"]!.GetValue(); + + return await httpClient.GetStreamAsync(downloadUrl); + } + + /// + /// Install plugin from stream + /// + /// Name of the plugin + /// Custom plugins download url. + /// Dependency set + /// Install by force for `update` + private async Task InstallPluginAsync( + string pluginName, + string? downloadUrl = null, + HashSet? installed = null, + bool overWrite = false) + { + installed ??= new HashSet(); + if (!installed.Add(pluginName)) return false; + if (!overWrite && PluginExists(pluginName)) return false; + + try + { + + using var stream = await DownloadPluginAsync(pluginName, Settings.Default.Plugins.Version, downloadUrl, Settings.Default.Plugins.Prerelease); + + using var zip = new ZipArchive(stream, ZipArchiveMode.Read); + var entry = zip.Entries.FirstOrDefault(p => p.Name == "config.json"); + if (entry is not null) + { + await using var es = entry.Open(); + await InstallDependenciesAsync(es, installed, downloadUrl); + } + zip.ExtractToDirectory("./", true); + return true; + } + catch (Exception ex) + { + ConsoleHelper.Error(ex?.InnerException?.Message ?? ex!.Message); + } + return false; + } + + /// + /// Install the dependency of the plugin + /// + /// plugin config path in temp + /// Dependency set + /// Custom plugin download url. + private async Task InstallDependenciesAsync(Stream config, HashSet installed, string? downloadUrl = null) + { + var dependency = new ConfigurationBuilder() + .AddJsonStream(config) + .Build() + .GetSection("Dependency"); + + if (!dependency.Exists()) return; + var dependencies = dependency.GetChildren().Select(p => p.Get()).ToArray(); + if (dependencies.Length == 0) return; + + foreach (var plugin in dependencies.Where(p => p is not null && !PluginExists(p))) + { + ConsoleHelper.Info($"Installing dependency: {plugin}"); + await InstallPluginAsync(plugin!, downloadUrl, installed); + } + } + + /// + /// Check that the plugin has all necessary files + /// + /// Name of the plugin + /// + private static bool PluginExists(string pluginName) + { + return Plugin.Plugins.Any(p => p.Name.Equals(pluginName, StringComparison.InvariantCultureIgnoreCase)); + } + + /// + /// Process "uninstall" command + /// + /// Plugin name + [ConsoleCommand("uninstall", Category = "Plugin Commands")] + private void OnUnInstallCommand(string pluginName) + { + if (!PluginExists(pluginName)) + { + ConsoleHelper.Error("Plugin not found"); + return; + } + + foreach (var p in Plugin.Plugins) + { + try + { + using var reader = File.OpenRead($"Plugins/{p.Name}/config.json"); + if (new ConfigurationBuilder() + .AddJsonStream(reader) + .Build() + .GetSection("Dependency") + .GetChildren() + .Select(s => s.Get()) + .Any(a => a is not null && a.Equals(pluginName, StringComparison.InvariantCultureIgnoreCase))) + { + ConsoleHelper.Error($"{pluginName} is required by other plugins."); + ConsoleHelper.Info("Info: ", $"If plugin is damaged try to reinstall."); + return; + } + } + catch (Exception) + { + // ignored + } + } + try + { + Directory.Delete($"Plugins/{pluginName}", true); + } + catch (IOException) { } + ConsoleHelper.Info("", "Uninstall successful, please restart neo-cli."); + } + + /// + /// Process "plugins" command + /// + [ConsoleCommand("plugins", Category = "Plugin Commands")] + private void OnPluginsCommand() + { + try + { + var plugins = GetPluginListAsync().GetAwaiter().GetResult()?.ToArray() ?? []; + var installedPlugins = Plugin.Plugins.ToList(); + + var maxLength = installedPlugins.Count == 0 ? 0 : installedPlugins.Max(s => s.Name.Length); + if (plugins.Length > 0) + { + maxLength = Math.Max(maxLength, plugins.Max(s => s.Length)); + } + + plugins.Select(s => (name: s, installedPlugin: Plugin.Plugins.SingleOrDefault(pp => string.Equals(pp.Name, s, StringComparison.InvariantCultureIgnoreCase)))) + .Concat(installedPlugins.Select(u => (name: u.Name, installedPlugin: (Plugin?)u)).Where(u => !plugins.Contains(u.name, StringComparer.InvariantCultureIgnoreCase))) + .OrderBy(u => u.name) + .ForEach((f) => + { + if (f.installedPlugin != null) + { + var tabs = f.name.Length < maxLength ? "\t" : string.Empty; + ConsoleHelper.Info("", $"[Installed]\t {f.name,6}{tabs}", " @", $"{f.installedPlugin.Version.ToString(3)} {f.installedPlugin.Description}"); + } + else + ConsoleHelper.Info($"[Not Installed]\t {f.name}"); + }); + } + catch (Exception ex) + { + ConsoleHelper.Error(ex!.InnerException?.Message ?? ex!.Message); + } + } + + private async Task> GetPluginListAsync() + { + using var httpClient = new HttpClient(); + + var asmName = Assembly.GetExecutingAssembly().GetName(); + httpClient.DefaultRequestHeaders.UserAgent.Add(new(asmName.Name!, asmName.Version!.ToString(3))); + + var json = await httpClient.GetFromJsonAsync(Settings.Default.Plugins.DownloadUrl) ?? throw new HttpRequestException($"Failed: {Settings.Default.Plugins.DownloadUrl}"); + return json.AsArray() + .Where(w => + w != null && + w["tag_name"]!.GetValue() == $"v{Settings.Default.Plugins.Version.ToString(3)}") + .SelectMany(s => s!["assets"]!.AsArray()) + .Select(s => Path.GetFileNameWithoutExtension(s!["name"]!.GetValue())); + } + } +} diff --git a/src/Neo.CLI/CLI/MainService.Tools.cs b/src/Neo.CLI/CLI/MainService.Tools.cs new file mode 100644 index 0000000000..66723d7df9 --- /dev/null +++ b/src/Neo.CLI/CLI/MainService.Tools.cs @@ -0,0 +1,493 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// MainService.Tools.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.ConsoleService; +using Neo.Cryptography.ECC; +using Neo.IO; +using Neo.SmartContract; +using Neo.VM; +using Neo.Wallets; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Numerics; +using System.Reflection; +using System.Text; + +namespace Neo.CLI +{ + partial class MainService + { + /// + /// Process "parse" command + /// + [ConsoleCommand("parse", Category = "Base Commands", Description = "Parse a value to its possible conversions.")] + private void OnParseCommand(string value) + { + value = Base64Fixed(value); + + var parseFunctions = new Dictionary>(); + var methods = GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Instance); + + foreach (var method in methods) + { + var attribute = method.GetCustomAttribute(); + if (attribute != null) + { + parseFunctions.Add(attribute.Description, (Func)Delegate.CreateDelegate(typeof(Func), this, method)); + } + } + + var any = false; + + foreach (var pair in parseFunctions) + { + var parseMethod = pair.Value; + var result = parseMethod(value); + + if (result != null) + { + Console.WriteLine($"{pair.Key,-30}\t{result}"); + any = true; + } + } + + if (!any) + { + ConsoleHelper.Warning($"Was not possible to convert: '{value}'"); + } + } + + /// + /// Little-endian to Big-endian + /// input: ce616f7f74617e0fc4b805583af2602a238df63f + /// output: 0x3ff68d232a60f23a5805b8c40f7e61747f6f61ce + /// + [ParseFunction("Little-endian to Big-endian")] + private string? LittleEndianToBigEndian(string hex) + { + try + { + if (!IsHex(hex)) return null; + return "0x" + hex.HexToBytes().Reverse().ToArray().ToHexString(); + } + catch (FormatException) + { + return null; + } + } + + /// + /// Big-endian to Little-endian + /// input: 0x3ff68d232a60f23a5805b8c40f7e61747f6f61ce + /// output: ce616f7f74617e0fc4b805583af2602a238df63f + /// + [ParseFunction("Big-endian to Little-endian")] + private string? BigEndianToLittleEndian(string hex) + { + try + { + var hasHexPrefix = hex.StartsWith("0x", StringComparison.InvariantCultureIgnoreCase); + hex = hasHexPrefix ? hex[2..] : hex; + if (!hasHexPrefix || !IsHex(hex)) return null; + return hex.HexToBytes().Reverse().ToArray().ToHexString(); + } + catch + { + return null; + } + } + + /// + /// String to Base64 + /// input: Hello World! + /// output: SGVsbG8gV29ybGQh + /// + [ParseFunction("String to Base64")] + private string? StringToBase64(string strParam) + { + try + { + var bytearray = Utility.StrictUTF8.GetBytes(strParam); + return Convert.ToBase64String(bytearray.AsSpan()); + } + catch + { + return null; + } + } + + /// + /// Big Integer to Base64 + /// input: 123456 + /// output: QOIB + /// + [ParseFunction("Big Integer to Base64")] + private string? NumberToBase64(string strParam) + { + try + { + if (!BigInteger.TryParse(strParam, out var number)) + { + return null; + } + var bytearray = number.ToByteArray(); + return Convert.ToBase64String(bytearray.AsSpan()); + } + catch + { + return null; + } + } + + private static bool IsHex(string str) => str.Length % 2 == 0 && str.All(c => (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); + + /// + /// Fix for Base64 strings containing unicode + /// input: DCECbzTesnBofh/Xng1SofChKkBC7jhVmLxCN1vk\u002B49xa2pBVuezJw== + /// output: DCECbzTesnBofh/Xng1SofChKkBC7jhVmLxCN1vk+49xa2pBVuezJw== + /// + /// Base64 strings containing unicode + /// Correct Base64 string + private static string Base64Fixed(string str) + { + var sb = new StringBuilder(); + for (var i = 0; i < str.Length; i++) + { + if (str[i] == '\\' && i + 5 < str.Length && str[i + 1] == 'u') + { + var hex = str.Substring(i + 2, 4); + if (IsHex(hex)) + { + var bts = new byte[2]; + bts[0] = (byte)int.Parse(hex.Substring(2, 2), NumberStyles.HexNumber); + bts[1] = (byte)int.Parse(hex.Substring(0, 2), NumberStyles.HexNumber); + sb.Append(Encoding.Unicode.GetString(bts)); + i += 5; + } + else + { + sb.Append(str[i]); + } + } + else + { + sb.Append(str[i]); + } + } + return sb.ToString(); + } + + /// + /// Address to ScriptHash (big-endian) + /// input: NejD7DJWzD48ZG4gXKDVZt3QLf1fpNe1PF + /// output: 0x3ff68d232a60f23a5805b8c40f7e61747f6f61ce + /// + [ParseFunction("Address to ScriptHash (big-endian)")] + private string? AddressToScripthash(string address) + { + try + { + var bigEndScript = address.ToScriptHash(NeoSystem.Settings.AddressVersion); + return bigEndScript.ToString(); + } + catch + { + return null; + } + } + + /// + /// Address to ScriptHash (blittleig-endian) + /// input: NejD7DJWzD48ZG4gXKDVZt3QLf1fpNe1PF + /// output: ce616f7f74617e0fc4b805583af2602a238df63f + /// + [ParseFunction("Address to ScriptHash (little-endian)")] + private string? AddressToScripthashLE(string address) + { + try + { + var bigEndScript = address.ToScriptHash(NeoSystem.Settings.AddressVersion); + return bigEndScript.ToArray().ToHexString(); + } + catch + { + return null; + } + } + + /// + /// Address to Base64 + /// input: NejD7DJWzD48ZG4gXKDVZt3QLf1fpNe1PF + /// output: zmFvf3Rhfg/EuAVYOvJgKiON9j8= + /// + [ParseFunction("Address to Base64")] + private string? AddressToBase64(string address) + { + try + { + var script = address.ToScriptHash(NeoSystem.Settings.AddressVersion); + return Convert.ToBase64String(script.ToArray().AsSpan()); + } + catch + { + return null; + } + } + + /// + /// ScriptHash to Address + /// input: 0x3ff68d232a60f23a5805b8c40f7e61747f6f61ce + /// output: NejD7DJWzD48ZG4gXKDVZt3QLf1fpNe1PF + /// + [ParseFunction("ScriptHash to Address")] + private string? ScripthashToAddress(string script) + { + try + { + UInt160 scriptHash; + if (script.StartsWith("0x")) + { + if (!UInt160.TryParse(script, out scriptHash)) + { + return null; + } + } + else + { + if (!UInt160.TryParse(script, out UInt160 littleEndScript)) + { + return null; + } + var bigEndScript = littleEndScript.ToArray().ToHexString(); + if (!UInt160.TryParse(bigEndScript, out scriptHash)) + { + return null; + } + } + + return scriptHash.ToAddress(NeoSystem.Settings.AddressVersion); + } + catch + { + return null; + } + } + + /// + /// Base64 to Address + /// input: zmFvf3Rhfg/EuAVYOvJgKiON9j8= + /// output: NejD7DJWzD48ZG4gXKDVZt3QLf1fpNe1PF + /// + [ParseFunction("Base64 to Address")] + private string? Base64ToAddress(string bytearray) + { + try + { + var result = Convert.FromBase64String(bytearray).Reverse().ToArray(); + var hex = result.ToHexString(); + + if (!UInt160.TryParse(hex, out var scripthash)) + { + return null; + } + + return scripthash.ToAddress(NeoSystem.Settings.AddressVersion); + } + catch + { + return null; + } + } + + /// + /// Base64 to String + /// input: SGVsbG8gV29ybGQh + /// output: Hello World! + /// + [ParseFunction("Base64 to String")] + private string? Base64ToString(string bytearray) + { + try + { + var result = Convert.FromBase64String(bytearray); + var utf8String = Utility.StrictUTF8.GetString(result); + return IsPrintable(utf8String) ? utf8String : null; + } + catch + { + return null; + } + } + + /// + /// Base64 to Big Integer + /// input: QOIB + /// output: 123456 + /// + [ParseFunction("Base64 to Big Integer")] + private string? Base64ToNumber(string bytearray) + { + try + { + var bytes = Convert.FromBase64String(bytearray); + var number = new BigInteger(bytes); + return number.ToString(); + } + catch + { + return null; + } + } + + /// + /// Public Key to Address + /// input: 03dab84c1243ec01ab2500e1a8c7a1546a26d734628180b0cf64e72bf776536997 + /// output: NU7RJrzNgCSnoPLxmcY7C72fULkpaGiSpJ + /// + [ParseFunction("Public Key to Address")] + private string? PublicKeyToAddress(string pubKey) + { + if (ECPoint.TryParse(pubKey, ECCurve.Secp256r1, out var publicKey) == false) + return null; + return Contract.CreateSignatureContract(publicKey) + .ScriptHash + .ToAddress(NeoSystem.Settings.AddressVersion); + } + + /// + /// WIF to Public Key + /// + [ParseFunction("WIF to Public Key")] + private string? WIFToPublicKey(string wif) + { + try + { + var privateKey = Wallet.GetPrivateKeyFromWIF(wif); + var account = new KeyPair(privateKey); + return account.PublicKey.ToArray().ToHexString(); + } + catch (Exception) + { + return null; + } + } + + /// + /// WIF to Address + /// + [ParseFunction("WIF to Address")] + private string? WIFToAddress(string wif) + { + try + { + var pubKey = WIFToPublicKey(wif); + return Contract.CreateSignatureContract(ECPoint.Parse(pubKey, ECCurve.Secp256r1)).ScriptHash.ToAddress(NeoSystem.Settings.AddressVersion); + } + catch (Exception) + { + return null; + } + } + + /// + /// Base64 Smart Contract Script Analysis + /// input: DARkYXRhAgBlzR0MFPdcrAXPVptVduMEs2lf1jQjxKIKDBT3XKwFz1abVXbjBLNpX9Y0I8SiChTAHwwIdHJhbnNmZXIMFKNSbimM12LkFYX/8KGvm2ttFxulQWJ9W1I= + /// output: + /// PUSHDATA1 data + /// PUSHINT32 500000000 + /// PUSHDATA1 0x0aa2c42334d65f69b304e376559b56cf05ac5cf7 + /// PUSHDATA1 0x0aa2c42334d65f69b304e376559b56cf05ac5cf7 + /// PUSH4 + /// PACK + /// PUSH15 + /// PUSHDATA1 transfer + /// PUSHDATA1 0xa51b176d6b9bafa1f0ff8515e462d78c296e52a3 + /// SYSCALL System.Contract.Call + /// + [ParseFunction("Base64 Smart Contract Script Analysis")] + private string? ScriptsToOpCode(string base64) + { + Script script; + try + { + var scriptData = Convert.FromBase64String(base64); + script = new Script(scriptData.ToArray(), true); + } + catch (Exception) + { + return null; + } + return ScriptsToOpCode(script); + } + + private string ScriptsToOpCode(Script script) + { + //Initialize all InteropService + var dic = new Dictionary(); + ApplicationEngine.Services.ToList().ForEach(p => dic.Add(p.Value.Hash, p.Value.Name)); + + //Analyzing Scripts + var ip = 0; + Instruction instruction; + var result = new List(); + while (ip < script.Length && (instruction = script.GetInstruction(ip)) != null) + { + ip += instruction.Size; + + var op = instruction.OpCode; + + if (op.ToString().StartsWith("PUSHINT")) + { + var operand = instruction.Operand.ToArray(); + result.Add($"{op} {new BigInteger(operand)}"); + } + else if (op == OpCode.SYSCALL) + { + var operand = instruction.Operand.ToArray(); + result.Add($"{op} {dic[BitConverter.ToUInt32(operand)]}"); + } + else + { + if (!instruction.Operand.IsEmpty && instruction.Operand.Length > 0) + { + var operand = instruction.Operand.ToArray(); + var ascii = Encoding.Default.GetString(operand); + ascii = ascii.Any(p => p < '0' || p > 'z') ? operand.ToHexString() : ascii; + + result.Add($"{op} {(operand.Length == 20 ? new UInt160(operand).ToString() : ascii)}"); + } + else + { + result.Add($"{op}"); + } + } + } + return Environment.NewLine + string.Join("\r\n", result.ToArray()); + } + + /// + /// Checks if the string is null or cannot be printed. + /// + /// + /// The string to test + /// + /// + /// Returns false if the string is null, or if it is empty, or if each character cannot be printed; + /// otherwise, returns true. + /// + private static bool IsPrintable(string value) + { + return !string.IsNullOrWhiteSpace(value) && value.Any(c => !char.IsControl(c)); + } + } +} diff --git a/src/Neo.CLI/CLI/MainService.Vote.cs b/src/Neo.CLI/CLI/MainService.Vote.cs new file mode 100644 index 0000000000..12cd48b3a9 --- /dev/null +++ b/src/Neo.CLI/CLI/MainService.Vote.cs @@ -0,0 +1,246 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// MainService.Vote.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.ConsoleService; +using Neo.Cryptography.ECC; +using Neo.Json; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.VM.Types; +using Neo.Wallets; +using System; +using System.Numerics; + +namespace Neo.CLI +{ + partial class MainService + { + /// + /// Process "register candidate" command + /// + /// register account scriptHash + [ConsoleCommand("register candidate", Category = "Vote Commands")] + private void OnRegisterCandidateCommand(UInt160 account) + { + var testGas = NativeContract.NEO.GetRegisterPrice(NeoSystem.StoreView) + (BigInteger)Math.Pow(10, NativeContract.GAS.Decimals) * 10; + if (NoWallet()) return; + WalletAccount currentAccount = CurrentWallet!.GetAccount(account); + + if (currentAccount == null) + { + ConsoleHelper.Warning("This address isn't in your wallet!"); + return; + } + else + { + if (currentAccount.Lock || currentAccount.WatchOnly) + { + ConsoleHelper.Warning("Locked or WatchOnly address."); + return; + } + } + + ECPoint? publicKey = currentAccount.GetKey()?.PublicKey; + byte[] script; + using (ScriptBuilder scriptBuilder = new()) + { + scriptBuilder.EmitDynamicCall(NativeContract.NEO.Hash, "registerCandidate", publicKey); + script = scriptBuilder.ToArray(); + } + + SendTransaction(script, account, (long)testGas); + } + + /// + /// Process "unregister candidate" command + /// + /// unregister account scriptHash + [ConsoleCommand("unregister candidate", Category = "Vote Commands")] + private void OnUnregisterCandidateCommand(UInt160 account) + { + if (NoWallet()) return; + WalletAccount currentAccount = CurrentWallet!.GetAccount(account); + + if (currentAccount == null) + { + ConsoleHelper.Warning("This address isn't in your wallet!"); + return; + } + else + { + if (currentAccount.Lock || currentAccount.WatchOnly) + { + ConsoleHelper.Warning("Locked or WatchOnly address."); + return; + } + } + + ECPoint? publicKey = currentAccount?.GetKey()?.PublicKey; + byte[] script; + using (ScriptBuilder scriptBuilder = new()) + { + scriptBuilder.EmitDynamicCall(NativeContract.NEO.Hash, "unregisterCandidate", publicKey); + script = scriptBuilder.ToArray(); + } + + SendTransaction(script, account); + } + + /// + /// Process "vote" command + /// + /// Sender account + /// Voting publicKey + [ConsoleCommand("vote", Category = "Vote Commands")] + private void OnVoteCommand(UInt160 senderAccount, ECPoint publicKey) + { + if (NoWallet()) return; + byte[] script; + using (ScriptBuilder scriptBuilder = new()) + { + scriptBuilder.EmitDynamicCall(NativeContract.NEO.Hash, "vote", senderAccount, publicKey); + script = scriptBuilder.ToArray(); + } + + SendTransaction(script, senderAccount); + } + + /// + /// Process "unvote" command + /// + /// Sender account + [ConsoleCommand("unvote", Category = "Vote Commands")] + private void OnUnvoteCommand(UInt160 senderAccount) + { + if (NoWallet()) return; + byte[] script; + using (ScriptBuilder scriptBuilder = new()) + { + scriptBuilder.EmitDynamicCall(NativeContract.NEO.Hash, "vote", senderAccount, null); + script = scriptBuilder.ToArray(); + } + + SendTransaction(script, senderAccount); + } + + /// + /// Process "get candidates" + /// + [ConsoleCommand("get candidates", Category = "Vote Commands")] + private void OnGetCandidatesCommand() + { + if (!OnInvokeWithResult(NativeContract.NEO.Hash, "getCandidates", out StackItem result, null, null, false)) return; + + var resJArray = (VM.Types.Array)result; + + if (resJArray.Count > 0) + { + Console.WriteLine(); + ConsoleHelper.Info("Candidates:"); + + foreach (var item in resJArray) + { + var value = (VM.Types.Array)item; + if (value is null) continue; + + Console.Write(((ByteString)value[0])?.GetSpan().ToHexString() + "\t"); + Console.WriteLine(((Integer)value[1]).GetInteger()); + } + } + } + + /// + /// Process "get committee" + /// + [ConsoleCommand("get committee", Category = "Vote Commands")] + private void OnGetCommitteeCommand() + { + if (!OnInvokeWithResult(NativeContract.NEO.Hash, "getCommittee", out StackItem result, null, null, false)) return; + + var resJArray = (VM.Types.Array)result; + + if (resJArray.Count > 0) + { + Console.WriteLine(); + ConsoleHelper.Info("Committee:"); + + foreach (var item in resJArray) + { + Console.WriteLine(((ByteString)item)?.GetSpan().ToHexString()); + } + } + } + + /// + /// Process "get next validators" + /// + [ConsoleCommand("get next validators", Category = "Vote Commands")] + private void OnGetNextBlockValidatorsCommand() + { + if (!OnInvokeWithResult(NativeContract.NEO.Hash, "getNextBlockValidators", out StackItem result, null, null, false)) return; + + var resJArray = (VM.Types.Array)result; + + if (resJArray.Count > 0) + { + Console.WriteLine(); + ConsoleHelper.Info("Next validators:"); + + foreach (var item in resJArray) + { + Console.WriteLine(((ByteString)item)?.GetSpan().ToHexString()); + } + } + } + + /// + /// Process "get accountstate" + /// + [ConsoleCommand("get accountstate", Category = "Vote Commands")] + private void OnGetAccountState(UInt160 address) + { + string notice = "No vote record!"; + var arg = new JObject + { + ["type"] = "Hash160", + ["value"] = address.ToString() + }; + + if (!OnInvokeWithResult(NativeContract.NEO.Hash, "getAccountState", out StackItem result, null, new JArray(arg))) return; + Console.WriteLine(); + if (result.IsNull) + { + ConsoleHelper.Warning(notice); + return; + } + var resJArray = (VM.Types.Array)result; + if (resJArray is null) + { + ConsoleHelper.Warning(notice); + return; + } + + foreach (StackItem value in resJArray) + { + if (value.IsNull) + { + ConsoleHelper.Warning(notice); + return; + } + } + var publickey = ECPoint.Parse(((ByteString)resJArray[2])?.GetSpan().ToHexString(), ECCurve.Secp256r1); + ConsoleHelper.Info("Voted: ", Contract.CreateSignatureRedeemScript(publickey).ToScriptHash().ToAddress(NeoSystem.Settings.AddressVersion)); + ConsoleHelper.Info("Amount: ", new BigDecimal(((Integer)resJArray[0]).GetInteger(), NativeContract.NEO.Decimals).ToString()); + ConsoleHelper.Info("Block: ", ((Integer)resJArray[1]).GetInteger().ToString()); + } + } +} diff --git a/src/Neo.CLI/CLI/MainService.Wallet.cs b/src/Neo.CLI/CLI/MainService.Wallet.cs new file mode 100644 index 0000000000..db33e89564 --- /dev/null +++ b/src/Neo.CLI/CLI/MainService.Wallet.cs @@ -0,0 +1,749 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// MainService.Wallet.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Akka.Actor; +using Neo.ConsoleService; +using Neo.Cryptography.ECC; +using Neo.Json; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using Neo.Wallets.NEP6; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Numerics; +using System.Threading.Tasks; +using static Neo.SmartContract.Helper; + +namespace Neo.CLI +{ + partial class MainService + { + /// + /// Process "open wallet" command + /// + /// Path + [ConsoleCommand("open wallet", Category = "Wallet Commands")] + private void OnOpenWallet(string path) + { + if (!File.Exists(path)) + { + ConsoleHelper.Error("File does not exist"); + return; + } + string password = ConsoleHelper.ReadUserInput("password", true); + if (password.Length == 0) + { + ConsoleHelper.Info("Cancelled"); + return; + } + try + { + OpenWallet(path, password); + } + catch (System.Security.Cryptography.CryptographicException) + { + ConsoleHelper.Error($"Failed to open file \"{path}\""); + } + } + + /// + /// Process "close wallet" command + /// + [ConsoleCommand("close wallet", Category = "Wallet Commands")] + private void OnCloseWalletCommand() + { + if (NoWallet()) return; + CurrentWallet = null; + ConsoleHelper.Info("Wallet is closed"); + } + + /// + /// Process "upgrade wallet" command + /// + [ConsoleCommand("upgrade wallet", Category = "Wallet Commands")] + private void OnUpgradeWalletCommand(string path) + { + if (Path.GetExtension(path).ToLowerInvariant() != ".db3") + { + ConsoleHelper.Warning("Can't upgrade the wallet file. Check if your wallet is in db3 format."); + return; + } + if (!File.Exists(path)) + { + ConsoleHelper.Error("File does not exist."); + return; + } + string password = ConsoleHelper.ReadUserInput("password", true); + if (password.Length == 0) + { + ConsoleHelper.Info("Cancelled"); + return; + } + string pathNew = Path.ChangeExtension(path, ".json"); + if (File.Exists(pathNew)) + { + ConsoleHelper.Warning($"File '{pathNew}' already exists"); + return; + } + NEP6Wallet.Migrate(pathNew, path, password, NeoSystem.Settings).Save(); + Console.WriteLine($"Wallet file upgrade complete. New wallet file has been auto-saved at: {pathNew}"); + } + + /// + /// Process "create address" command + /// + /// Count + [ConsoleCommand("create address", Category = "Wallet Commands")] + private void OnCreateAddressCommand(ushort count = 1) + { + if (NoWallet()) return; + string path = "address.txt"; + if (File.Exists(path)) + { + if (!ConsoleHelper.ReadUserInput($"The file '{path}' already exists, do you want to overwrite it? (yes|no)", false).IsYes()) + { + return; + } + } + + List addresses = new List(); + using (var percent = new ConsolePercent(0, count)) + { + Parallel.For(0, count, (i) => + { + WalletAccount account = CurrentWallet!.CreateAccount(); + lock (addresses) + { + addresses.Add(account.Address); + percent.Value++; + } + }); + } + + if (CurrentWallet is NEP6Wallet wallet) + wallet.Save(); + + Console.WriteLine($"Export addresses to {path}"); + File.WriteAllLines(path, addresses); + } + + /// + /// Process "delete address" command + /// + /// Address + [ConsoleCommand("delete address", Category = "Wallet Commands")] + private void OnDeleteAddressCommand(UInt160 address) + { + if (NoWallet()) return; + + if (ConsoleHelper.ReadUserInput($"Warning: Irrevocable operation!\nAre you sure to delete account {address.ToAddress(NeoSystem.Settings.AddressVersion)}? (no|yes)").IsYes()) + { + if (CurrentWallet!.DeleteAccount(address)) + { + if (CurrentWallet is NEP6Wallet wallet) + { + wallet.Save(); + } + ConsoleHelper.Info($"Address {address} deleted."); + } + else + { + ConsoleHelper.Warning($"Address {address} doesn't exist."); + } + } + } + + /// + /// Process "export key" command + /// + /// Path + /// ScriptHash + [ConsoleCommand("export key", Category = "Wallet Commands")] + private void OnExportKeyCommand(string? path = null, UInt160? scriptHash = null) + { + if (NoWallet()) return; + if (path != null && File.Exists(path)) + { + ConsoleHelper.Error($"File '{path}' already exists"); + return; + } + string password = ConsoleHelper.ReadUserInput("password", true); + if (password.Length == 0) + { + ConsoleHelper.Info("Cancelled"); + return; + } + if (!CurrentWallet!.VerifyPassword(password)) + { + ConsoleHelper.Error("Incorrect password"); + return; + } + IEnumerable keys; + if (scriptHash == null) + keys = CurrentWallet.GetAccounts().Where(p => p.HasKey).Select(p => p.GetKey()); + else + { + var account = CurrentWallet.GetAccount(scriptHash); + keys = account?.HasKey != true ? Array.Empty() : new[] { account.GetKey() }; + } + if (path == null) + foreach (KeyPair key in keys) + Console.WriteLine(key.Export()); + else + File.WriteAllLines(path, keys.Select(p => p.Export())); + } + + /// + /// Process "create wallet" command + /// + [ConsoleCommand("create wallet", Category = "Wallet Commands")] + private void OnCreateWalletCommand(string path, string? wifOrFile = null) + { + string password = ConsoleHelper.ReadUserInput("password", true); + if (password.Length == 0) + { + ConsoleHelper.Info("Cancelled"); + return; + } + string password2 = ConsoleHelper.ReadUserInput("repeat password", true); + if (password != password2) + { + ConsoleHelper.Error("Two passwords not match."); + return; + } + if (File.Exists(path)) + { + Console.WriteLine("This wallet already exists, please create another one."); + return; + } + bool createDefaultAccount = wifOrFile is null; + CreateWallet(path, password, createDefaultAccount); + if (!createDefaultAccount) OnImportKeyCommand(wifOrFile!); + } + + /// + /// Process "import multisigaddress" command + /// + /// Required signatures + /// Public keys + [ConsoleCommand("import multisigaddress", Category = "Wallet Commands")] + private void OnImportMultisigAddress(ushort m, ECPoint[] publicKeys) + { + if (NoWallet()) return; + int n = publicKeys.Length; + + if (m < 1 || m > n || n > 1024) + { + ConsoleHelper.Error("Invalid parameters."); + return; + } + + Contract multiSignContract = Contract.CreateMultiSigContract(m, publicKeys); + KeyPair? keyPair = CurrentWallet!.GetAccounts().FirstOrDefault(p => p.HasKey && publicKeys.Contains(p.GetKey().PublicKey))?.GetKey(); + + CurrentWallet.CreateAccount(multiSignContract, keyPair); + if (CurrentWallet is NEP6Wallet wallet) + wallet.Save(); + + ConsoleHelper.Info("Multisig. Addr.: ", multiSignContract.ScriptHash.ToAddress(NeoSystem.Settings.AddressVersion)); + } + + /// + /// Process "import key" command + /// + [ConsoleCommand("import key", Category = "Wallet Commands")] + private void OnImportKeyCommand(string wifOrFile) + { + if (NoWallet()) return; + byte[]? prikey = null; + try + { + prikey = Wallet.GetPrivateKeyFromWIF(wifOrFile); + } + catch (FormatException) { } + if (prikey == null) + { + var fileInfo = new FileInfo(wifOrFile); + + if (!fileInfo.Exists) + { + ConsoleHelper.Error($"File '{fileInfo.FullName}' doesn't exists"); + return; + } + + if (wifOrFile.Length > 1024 * 1024) + { + if (!ConsoleHelper.ReadUserInput($"The file '{fileInfo.FullName}' is too big, do you want to continue? (yes|no)", false).IsYes()) + { + return; + } + } + + string[] lines = File.ReadAllLines(fileInfo.FullName).Where(u => !string.IsNullOrEmpty(u)).ToArray(); + using (var percent = new ConsolePercent(0, lines.Length)) + { + for (int i = 0; i < lines.Length; i++) + { + if (lines[i].Length == 64) + prikey = lines[i].HexToBytes(); + else + prikey = Wallet.GetPrivateKeyFromWIF(lines[i]); + CurrentWallet!.CreateAccount(prikey); + Array.Clear(prikey, 0, prikey.Length); + percent.Value++; + } + } + } + else + { + WalletAccount account = CurrentWallet!.CreateAccount(prikey); + Array.Clear(prikey, 0, prikey.Length); + ConsoleHelper.Info("Address: ", account.Address); + ConsoleHelper.Info(" Pubkey: ", account.GetKey().PublicKey.EncodePoint(true).ToHexString()); + } + if (CurrentWallet is NEP6Wallet wallet) + wallet.Save(); + } + + /// + /// Process "import watchonly" command + /// + [ConsoleCommand("import watchonly", Category = "Wallet Commands")] + private void OnImportWatchOnlyCommand(string addressOrFile) + { + if (NoWallet()) return; + UInt160? address = null; + try + { + address = StringToAddress(addressOrFile, NeoSystem.Settings.AddressVersion); + } + catch (FormatException) { } + if (address is null) + { + var fileInfo = new FileInfo(addressOrFile); + + if (!fileInfo.Exists) + { + ConsoleHelper.Warning($"File '{fileInfo.FullName}' doesn't exists"); + return; + } + + if (fileInfo.Length > 1024 * 1024) + { + if (!ConsoleHelper.ReadUserInput($"The file '{fileInfo.FullName}' is too big, do you want to continue? (yes|no)", false).IsYes()) + { + return; + } + } + + string[] lines = File.ReadAllLines(fileInfo.FullName).Where(u => !string.IsNullOrEmpty(u)).ToArray(); + using (var percent = new ConsolePercent(0, lines.Length)) + { + for (int i = 0; i < lines.Length; i++) + { + address = StringToAddress(lines[i], NeoSystem.Settings.AddressVersion); + CurrentWallet!.CreateAccount(address); + percent.Value++; + } + } + } + else + { + WalletAccount account = CurrentWallet!.GetAccount(address); + if (account is not null) + { + ConsoleHelper.Warning("This address is already in your wallet"); + } + else + { + account = CurrentWallet.CreateAccount(address); + ConsoleHelper.Info("Address: ", account.Address); + } + } + if (CurrentWallet is NEP6Wallet wallet) + wallet.Save(); + } + + /// + /// Process "list address" command + /// + [ConsoleCommand("list address", Category = "Wallet Commands")] + private void OnListAddressCommand() + { + if (NoWallet()) return; + var snapshot = NeoSystem.StoreView; + foreach (var account in CurrentWallet!.GetAccounts()) + { + var contract = account.Contract; + var type = "Nonstandard"; + + if (account.WatchOnly) + { + type = "WatchOnly"; + } + else if (IsMultiSigContract(contract.Script)) + { + type = "MultiSignature"; + } + else if (IsSignatureContract(contract.Script)) + { + type = "Standard"; + } + else if (NativeContract.ContractManagement.GetContract(snapshot, account.ScriptHash) != null) + { + type = "Deployed-Nonstandard"; + } + + ConsoleHelper.Info(" Address: ", $"{account.Address}\t{type}"); + ConsoleHelper.Info("ScriptHash: ", $"{account.ScriptHash}\n"); + } + } + + /// + /// Process "list asset" command + /// + [ConsoleCommand("list asset", Category = "Wallet Commands")] + private void OnListAssetCommand() + { + var snapshot = NeoSystem.StoreView; + if (NoWallet()) return; + foreach (UInt160 account in CurrentWallet!.GetAccounts().Select(p => p.ScriptHash)) + { + Console.WriteLine(account.ToAddress(NeoSystem.Settings.AddressVersion)); + ConsoleHelper.Info("NEO: ", $"{CurrentWallet.GetBalance(snapshot, NativeContract.NEO.Hash, account)}"); + ConsoleHelper.Info("GAS: ", $"{CurrentWallet.GetBalance(snapshot, NativeContract.GAS.Hash, account)}"); + Console.WriteLine(); + } + Console.WriteLine("----------------------------------------------------"); + ConsoleHelper.Info("Total: NEO: ", $"{CurrentWallet.GetAvailable(snapshot, NativeContract.NEO.Hash),10} ", "GAS: ", $"{CurrentWallet.GetAvailable(snapshot, NativeContract.GAS.Hash),18}"); + Console.WriteLine(); + ConsoleHelper.Info("NEO hash: ", NativeContract.NEO.Hash.ToString()); + ConsoleHelper.Info("GAS hash: ", NativeContract.GAS.Hash.ToString()); + } + + /// + /// Process "list key" command + /// + [ConsoleCommand("list key", Category = "Wallet Commands")] + private void OnListKeyCommand() + { + if (NoWallet()) return; + foreach (WalletAccount account in CurrentWallet!.GetAccounts().Where(p => p.HasKey)) + { + ConsoleHelper.Info(" Address: ", account.Address); + ConsoleHelper.Info("ScriptHash: ", account.ScriptHash.ToString()); + ConsoleHelper.Info(" PublicKey: ", account.GetKey().PublicKey.EncodePoint(true).ToHexString()); + Console.WriteLine(); + } + } + + /// + /// Process "sign" command + /// + /// Json object to sign + [ConsoleCommand("sign", Category = "Wallet Commands")] + private void OnSignCommand(JObject jsonObjectToSign) + { + if (NoWallet()) return; + + if (jsonObjectToSign == null) + { + ConsoleHelper.Warning("You must input JSON object pending signature data."); + return; + } + try + { + var snapshot = NeoSystem.StoreView; + ContractParametersContext context = ContractParametersContext.Parse(jsonObjectToSign.ToString(), snapshot); + if (context.Network != NeoSystem.Settings.Network) + { + ConsoleHelper.Warning("Network mismatch."); + return; + } + else if (!CurrentWallet!.Sign(context)) + { + ConsoleHelper.Warning("Non-existent private key in wallet."); + return; + } + ConsoleHelper.Info("Signed Output: ", $"{Environment.NewLine}{context}"); + } + catch (Exception e) + { + ConsoleHelper.Error(GetExceptionMessage(e)); + } + } + + /// + /// Process "send" command + /// + /// Asset id + /// To + /// Amount + /// From + /// Data + /// Signer's accounts + [ConsoleCommand("send", Category = "Wallet Commands")] + private void OnSendCommand(UInt160 asset, UInt160 to, string amount, UInt160? from = null, string? data = null, UInt160[]? signerAccounts = null) + { + if (NoWallet()) return; + string password = ConsoleHelper.ReadUserInput("password", true); + if (password.Length == 0) + { + ConsoleHelper.Info("Cancelled"); + return; + } + if (!CurrentWallet!.VerifyPassword(password)) + { + ConsoleHelper.Error("Incorrect password"); + return; + } + + var snapshot = NeoSystem.StoreView; + Transaction tx; + AssetDescriptor descriptor = new(snapshot, NeoSystem.Settings, asset); + if (!BigDecimal.TryParse(amount, descriptor.Decimals, out BigDecimal decimalAmount) || decimalAmount.Sign <= 0) + { + ConsoleHelper.Error("Incorrect Amount Format"); + return; + } + try + { + tx = CurrentWallet.MakeTransaction(snapshot, new[] + { + new TransferOutput + { + AssetId = asset, + Value = decimalAmount, + ScriptHash = to, + Data = data + } + }, from: from, cosigners: signerAccounts?.Select(p => new Signer + { + // default access for transfers should be valid only for first invocation + Scopes = WitnessScope.CalledByEntry, + Account = p + }) + .ToArray() ?? Array.Empty()); + } + catch (Exception e) + { + ConsoleHelper.Error(GetExceptionMessage(e)); + return; + } + + if (tx == null) + { + ConsoleHelper.Warning("Insufficient funds"); + return; + } + + ConsoleHelper.Info( + "Send To: ", $"{to.ToAddress(NeoSystem.Settings.AddressVersion)}\n", + "Network fee: ", $"{new BigDecimal((BigInteger)tx.NetworkFee, NativeContract.GAS.Decimals)}\t", + "Total fee: ", $"{new BigDecimal((BigInteger)(tx.SystemFee + tx.NetworkFee), NativeContract.GAS.Decimals)} GAS"); + if (!ConsoleHelper.ReadUserInput("Relay tx? (no|yes)").IsYes()) + { + return; + } + SignAndSendTx(NeoSystem.StoreView, tx); + } + + /// + /// Process "cancel" command + /// + /// conflict txid + /// Transaction's sender + /// Signer's accounts + [ConsoleCommand("cancel", Category = "Wallet Commands")] + private void OnCancelCommand(UInt256 txid, UInt160? sender = null, UInt160[]? signerAccounts = null) + { + if (NoWallet()) return; + + TransactionState state = NativeContract.Ledger.GetTransactionState(NeoSystem.StoreView, txid); + if (state != null) + { + ConsoleHelper.Error("This tx is already confirmed, can't be cancelled."); + return; + } + + var conflict = new TransactionAttribute[] { new Conflicts() { Hash = txid } }; + Signer[] signers = Array.Empty(); + if (sender != null) + { + if (signerAccounts == null) + signerAccounts = new UInt160[1] { sender }; + else if (signerAccounts.Contains(sender) && signerAccounts[0] != sender) + { + var signersList = signerAccounts.ToList(); + signersList.Remove(sender); + signerAccounts = signersList.Prepend(sender).ToArray(); + } + else if (!signerAccounts.Contains(sender)) + { + signerAccounts = signerAccounts.Prepend(sender).ToArray(); + } + signers = signerAccounts.Select(p => new Signer() { Account = p, Scopes = WitnessScope.None }).ToArray(); + } + + Transaction tx = new() + { + Signers = signers, + Attributes = conflict, + Witnesses = Array.Empty(), + }; + + try + { + using ScriptBuilder scriptBuilder = new(); + scriptBuilder.Emit(OpCode.RET); + tx = CurrentWallet!.MakeTransaction(NeoSystem.StoreView, scriptBuilder.ToArray(), sender, signers, conflict); + } + catch (InvalidOperationException e) + { + ConsoleHelper.Error(GetExceptionMessage(e)); + return; + } + + if (NeoSystem.MemPool.TryGetValue(txid, out Transaction conflictTx)) + { + tx.NetworkFee = Math.Max(tx.NetworkFee, conflictTx.NetworkFee) + 1; + } + else + { + var snapshot = NeoSystem.StoreView; + AssetDescriptor descriptor = new(snapshot, NeoSystem.Settings, NativeContract.GAS.Hash); + string extracFee = ConsoleHelper.ReadUserInput("This tx is not in mempool, please input extra fee (datoshi) manually"); + if (!BigDecimal.TryParse(extracFee, descriptor.Decimals, out BigDecimal decimalExtraFee) || decimalExtraFee.Sign <= 0) + { + ConsoleHelper.Error("Incorrect Amount Format"); + return; + } + tx.NetworkFee += (long)decimalExtraFee.Value; + }; + + ConsoleHelper.Info("Network fee: ", + $"{new BigDecimal((BigInteger)tx.NetworkFee, NativeContract.GAS.Decimals)} GAS\t", + "Total fee: ", + $"{new BigDecimal((BigInteger)(tx.SystemFee + tx.NetworkFee), NativeContract.GAS.Decimals)} GAS"); + if (!ConsoleHelper.ReadUserInput("Relay tx? (no|yes)").IsYes()) + { + return; + } + SignAndSendTx(NeoSystem.StoreView, tx); + } + + /// + /// Process "show gas" command + /// + [ConsoleCommand("show gas", Category = "Wallet Commands")] + private void OnShowGasCommand() + { + if (NoWallet()) return; + BigInteger gas = BigInteger.Zero; + var snapshot = NeoSystem.StoreView; + uint height = NativeContract.Ledger.CurrentIndex(snapshot) + 1; + foreach (UInt160 account in CurrentWallet!.GetAccounts().Select(p => p.ScriptHash)) + gas += NativeContract.NEO.UnclaimedGas(snapshot, account, height); + ConsoleHelper.Info("Unclaimed gas: ", new BigDecimal(gas, NativeContract.GAS.Decimals).ToString()); + } + + /// + /// Process "change password" command + /// + [ConsoleCommand("change password", Category = "Wallet Commands")] + private void OnChangePasswordCommand() + { + if (NoWallet()) return; + string oldPassword = ConsoleHelper.ReadUserInput("password", true); + if (oldPassword.Length == 0) + { + ConsoleHelper.Info("Cancelled"); + return; + } + if (!CurrentWallet!.VerifyPassword(oldPassword)) + { + ConsoleHelper.Error("Incorrect password"); + return; + } + string newPassword = ConsoleHelper.ReadUserInput("New password", true); + string newPasswordReEntered = ConsoleHelper.ReadUserInput("Re-Enter Password", true); + if (!newPassword.Equals(newPasswordReEntered)) + { + ConsoleHelper.Error("Two passwords entered are inconsistent!"); + return; + } + + if (CurrentWallet is NEP6Wallet wallet) + { + string backupFile = wallet.Path + ".bak"; + if (!File.Exists(wallet.Path) || File.Exists(backupFile)) + { + ConsoleHelper.Error("Wallet backup fail"); + return; + } + try + { + File.Copy(wallet.Path, backupFile); + } + catch (IOException) + { + ConsoleHelper.Error("Wallet backup fail"); + return; + } + } + + bool succeed = CurrentWallet.ChangePassword(oldPassword, newPassword); + if (succeed) + { + if (CurrentWallet is NEP6Wallet nep6Wallet) + nep6Wallet.Save(); + Console.WriteLine("Password changed successfully"); + } + else + { + ConsoleHelper.Error("Failed to change password"); + } + } + + private void SignAndSendTx(DataCache snapshot, Transaction tx) + { + if (NoWallet()) return; + + ContractParametersContext context; + try + { + context = new ContractParametersContext(snapshot, tx, NeoSystem.Settings.Network); + } + catch (InvalidOperationException e) + { + ConsoleHelper.Error("Failed creating contract params: " + GetExceptionMessage(e)); + throw; + } + CurrentWallet!.Sign(context); + if (context.Completed) + { + tx.Witnesses = context.GetWitnesses(); + NeoSystem.Blockchain.Tell(tx); + ConsoleHelper.Info("Signed and relayed transaction with hash:\n", $"{tx.Hash}"); + } + else + { + ConsoleHelper.Info("Incomplete signature:\n", $"{context}"); + } + } + } +} diff --git a/src/Neo.CLI/CLI/MainService.cs b/src/Neo.CLI/CLI/MainService.cs new file mode 100644 index 0000000000..88967c82ea --- /dev/null +++ b/src/Neo.CLI/CLI/MainService.cs @@ -0,0 +1,733 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// MainService.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Akka.Actor; +using Neo.ConsoleService; +using Neo.Cryptography.ECC; +using Neo.IO; +using Neo.Json; +using Neo.Ledger; +using Neo.Network.P2P; +using Neo.Network.P2P.Payloads; +using Neo.Plugins; +using Neo.SmartContract; +using Neo.SmartContract.Manifest; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.VM.Types; +using Neo.Wallets; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Net; +using System.Numerics; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Array = System.Array; + +namespace Neo.CLI +{ + public partial class MainService : ConsoleServiceBase, IWalletProvider + { + public event EventHandler? WalletChanged = null; + + public const long TestModeGas = 20_00000000; + + private Wallet? _currentWallet; + + public Wallet? CurrentWallet + { + get => _currentWallet; + private set + { + _currentWallet = value; + WalletChanged?.Invoke(this, value); + } + } + + private NeoSystem? _neoSystem; + public NeoSystem NeoSystem + { + get => _neoSystem!; + private set => _neoSystem = value; + } + + private LocalNode? _localNode; + + public LocalNode LocalNode + { + get => _localNode!; + private set => _localNode = value; + } + + protected override string Prompt => "neo"; + public override string ServiceName => "NEO-CLI"; + + /// + /// Constructor + /// + public MainService() : base() + { + RegisterCommandHandler(false, str => StringToAddress(str, NeoSystem.Settings.AddressVersion)); + RegisterCommandHandler(false, UInt256.Parse); + RegisterCommandHandler(str => str.Select(u => UInt256.Parse(u.Trim())).ToArray()); + RegisterCommandHandler(arr => arr.Select(str => StringToAddress(str, NeoSystem.Settings.AddressVersion)).ToArray()); + RegisterCommandHandler(str => ECPoint.Parse(str.Trim(), ECCurve.Secp256r1)); + RegisterCommandHandler(str => str.Select(u => ECPoint.Parse(u.Trim(), ECCurve.Secp256r1)).ToArray()); + RegisterCommandHandler(str => JToken.Parse(str)!); + RegisterCommandHandler(str => (JObject)JToken.Parse(str)!); + RegisterCommandHandler(str => decimal.Parse(str, CultureInfo.InvariantCulture)); + RegisterCommandHandler(obj => (JArray)obj); + + RegisterCommand(this); + + Initialize_Logger(); + } + + internal UInt160 StringToAddress(string input, byte version) + { + switch (input.ToLowerInvariant()) + { + case "neo": return NativeContract.NEO.Hash; + case "gas": return NativeContract.GAS.Hash; + } + + if (input.IndexOf('.') > 0 && input.LastIndexOf('.') < input.Length) + { + return ResolveNeoNameServiceAddress(input); + } + + // Try to parse as UInt160 + + if (UInt160.TryParse(input, out var addr)) + { + return addr; + } + + // Accept wallet format + + return input.ToScriptHash(version); + } + + Wallet? IWalletProvider.GetWallet() + { + return CurrentWallet; + } + + public override void RunConsole() + { + Console.ForegroundColor = ConsoleColor.DarkGreen; + + var cliV = Assembly.GetAssembly(typeof(Program))!.GetVersion(); + var neoV = Assembly.GetAssembly(typeof(NeoSystem))!.GetVersion(); + var vmV = Assembly.GetAssembly(typeof(ExecutionEngine))!.GetVersion(); + Console.WriteLine($"{ServiceName} v{cliV} - NEO v{neoV} - NEO-VM v{vmV}"); + Console.WriteLine(); + + base.RunConsole(); + } + + public void CreateWallet(string path, string password, bool createDefaultAccount = true) + { + Wallet wallet = Wallet.Create(null, path, password, NeoSystem.Settings); + if (wallet == null) + { + ConsoleHelper.Warning("Wallet files in that format are not supported, please use a .json or .db3 file extension."); + return; + } + if (createDefaultAccount) + { + WalletAccount account = wallet.CreateAccount(); + ConsoleHelper.Info(" Address: ", account.Address); + ConsoleHelper.Info(" Pubkey: ", account.GetKey().PublicKey.EncodePoint(true).ToHexString()); + ConsoleHelper.Info("ScriptHash: ", $"{account.ScriptHash}"); + } + wallet.Save(); + CurrentWallet = wallet; + } + + private IEnumerable GetBlocks(Stream stream, bool read_start = false) + { + using BinaryReader r = new BinaryReader(stream); + uint start = read_start ? r.ReadUInt32() : 0; + uint count = r.ReadUInt32(); + uint end = start + count - 1; + uint currentHeight = NativeContract.Ledger.CurrentIndex(NeoSystem.StoreView); + if (end <= currentHeight) yield break; + for (uint height = start; height <= end; height++) + { + var size = r.ReadInt32(); + if (size > Message.PayloadMaxSize) + throw new ArgumentException($"Block {height} exceeds the maximum allowed size"); + + byte[] array = r.ReadBytes(size); + if (height > currentHeight) + { + Block block = array.AsSerializable(); + yield return block; + } + } + } + + private IEnumerable GetBlocksFromFile() + { + const string pathAcc = "chain.acc"; + if (File.Exists(pathAcc)) + using (FileStream fs = new(pathAcc, FileMode.Open, FileAccess.Read, FileShare.Read)) + foreach (var block in GetBlocks(fs)) + yield return block; + + const string pathAccZip = pathAcc + ".zip"; + if (File.Exists(pathAccZip)) + using (FileStream fs = new(pathAccZip, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (ZipArchive zip = new(fs, ZipArchiveMode.Read)) + using (Stream? zs = zip.GetEntry(pathAcc)?.Open()) + { + if (zs is not null) + { + foreach (var block in GetBlocks(zs)) + yield return block; + } + } + + var paths = Directory.EnumerateFiles(".", "chain.*.acc", SearchOption.TopDirectoryOnly).Concat(Directory.EnumerateFiles(".", "chain.*.acc.zip", SearchOption.TopDirectoryOnly)).Select(p => new + { + FileName = Path.GetFileName(p), + Start = uint.Parse(Regex.Match(p, @"\d+").Value), + IsCompressed = p.EndsWith(".zip") + }).OrderBy(p => p.Start); + + uint height = NativeContract.Ledger.CurrentIndex(NeoSystem.StoreView); + foreach (var path in paths) + { + if (path.Start > height + 1) break; + if (path.IsCompressed) + using (FileStream fs = new(path.FileName, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (ZipArchive zip = new(fs, ZipArchiveMode.Read)) + using (Stream? zs = zip.GetEntry(Path.GetFileNameWithoutExtension(path.FileName))?.Open()) + { + if (zs is not null) + { + foreach (var block in GetBlocks(zs, true)) + yield return block; + } + } + else + using (FileStream fs = new(path.FileName, FileMode.Open, FileAccess.Read, FileShare.Read)) + foreach (var block in GetBlocks(fs, true)) + yield return block; + } + } + + private bool NoWallet() + { + if (CurrentWallet != null) return false; + ConsoleHelper.Error("You have to open the wallet first."); + return true; + } + + private byte[] LoadDeploymentScript(string nefFilePath, string? manifestFilePath, JObject? data, out NefFile nef, out ContractManifest manifest) + { + if (string.IsNullOrEmpty(manifestFilePath)) + { + manifestFilePath = Path.ChangeExtension(nefFilePath, ".manifest.json"); + } + + // Read manifest + + var info = new FileInfo(manifestFilePath); + if (!info.Exists || info.Length >= Transaction.MaxTransactionSize) + { + throw new ArgumentException(nameof(manifestFilePath)); + } + + manifest = ContractManifest.Parse(File.ReadAllBytes(manifestFilePath)); + + // Read nef + + info = new FileInfo(nefFilePath); + if (!info.Exists || info.Length >= Transaction.MaxTransactionSize) + { + throw new ArgumentException(nameof(nefFilePath)); + } + + nef = File.ReadAllBytes(nefFilePath).AsSerializable(); + + ContractParameter? dataParameter = null; + if (data is not null) + try + { + dataParameter = ContractParameter.FromJson(data); + } + catch + { + throw new FormatException("invalid data"); + } + + // Basic script checks + nef.Script.IsScriptValid(manifest.Abi); + + // Build script + + using (ScriptBuilder sb = new ScriptBuilder()) + { + if (dataParameter is not null) + sb.EmitDynamicCall(NativeContract.ContractManagement.Hash, "deploy", nef.ToArray(), manifest.ToJson().ToString(), dataParameter); + else + sb.EmitDynamicCall(NativeContract.ContractManagement.Hash, "deploy", nef.ToArray(), manifest.ToJson().ToString()); + return sb.ToArray(); + } + } + + private byte[] LoadUpdateScript(UInt160 scriptHash, string nefFilePath, string manifestFilePath, JObject? data, out NefFile nef, out ContractManifest manifest) + { + if (string.IsNullOrEmpty(manifestFilePath)) + { + manifestFilePath = Path.ChangeExtension(nefFilePath, ".manifest.json"); + } + + // Read manifest + + var info = new FileInfo(manifestFilePath); + if (!info.Exists || info.Length >= Transaction.MaxTransactionSize) + { + throw new ArgumentException(nameof(manifestFilePath)); + } + + manifest = ContractManifest.Parse(File.ReadAllBytes(manifestFilePath)); + + // Read nef + + info = new FileInfo(nefFilePath); + if (!info.Exists || info.Length >= Transaction.MaxTransactionSize) + { + throw new ArgumentException(nameof(nefFilePath)); + } + + nef = File.ReadAllBytes(nefFilePath).AsSerializable(); + + ContractParameter? dataParameter = null; + if (data is not null) + try + { + dataParameter = ContractParameter.FromJson(data); + } + catch + { + throw new FormatException("invalid data"); + } + + // Basic script checks + nef.Script.IsScriptValid(manifest.Abi); + + // Build script + + using (ScriptBuilder sb = new ScriptBuilder()) + { + if (dataParameter is null) + sb.EmitDynamicCall(scriptHash, "update", nef.ToArray(), manifest.ToJson().ToString()); + else + sb.EmitDynamicCall(scriptHash, "update", nef.ToArray(), manifest.ToJson().ToString(), dataParameter); + return sb.ToArray(); + } + } + + public override bool OnStart(string[] args) + { + if (!base.OnStart(args)) return false; + return OnStartWithCommandLine(args) != 1; + } + + public override void OnStop() + { + base.OnStop(); + Stop(); + } + + public void OpenWallet(string path, string password) + { + if (!File.Exists(path)) + { + throw new FileNotFoundException(); + } + + CurrentWallet = Wallet.Open(path, password, NeoSystem.Settings) ?? throw new NotSupportedException(); + } + + public async void Start(CommandLineOptions options) + { + if (NeoSystem != null) return; + bool verifyImport = !(options.NoVerify ?? false); + + ProtocolSettings protocol = ProtocolSettings.Load("config.json"); + CustomProtocolSettings(options, protocol); + CustomApplicationSettings(options, Settings.Default); + try + { + NeoSystem = new NeoSystem(protocol, Settings.Default.Storage.Engine, + string.Format(Settings.Default.Storage.Path, protocol.Network.ToString("X8"))); + } + catch (DllNotFoundException ex) when (ex.Message.Contains("libleveldb")) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + if (File.Exists("libleveldb.dll")) + { + DisplayError("Dependency DLL not found, please install Microsoft Visual C++ Redistributable.", + "See https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist"); + } + else + { + DisplayError("DLL not found, please get libleveldb.dll."); + } + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + DisplayError("Shared library libleveldb.dylib not found, please get libleveldb.dylib.", + "From https://github.com/neo-project/neo/releases"); + } + else + { + DisplayError("Neo CLI is broken, please reinstall it.", + "From https://github.com/neo-project/neo/releases"); + } + + } + catch (DllNotFoundException) + { + DisplayError("Neo CLI is broken, please reinstall it.", + "From https://github.com/neo-project/neo/releases"); + } + + NeoSystem.AddService(this); + + LocalNode = NeoSystem.LocalNode.Ask(new LocalNode.GetInstance()).Result; + + // installing plugins + var installTasks = options.Plugins?.Select(p => p).Where(p => !string.IsNullOrEmpty(p)).ToList().Select(p => InstallPluginAsync(p)); + if (installTasks is not null) + { + await Task.WhenAll(installTasks); + } + foreach (var plugin in Plugin.Plugins) + { + // Register plugins commands + + RegisterCommand(plugin, plugin.Name); + } + + using (IEnumerator blocksBeingImported = GetBlocksFromFile().GetEnumerator()) + { + while (true) + { + List blocksToImport = new List(); + for (int i = 0; i < 10; i++) + { + if (!blocksBeingImported.MoveNext()) break; + blocksToImport.Add(blocksBeingImported.Current); + } + if (blocksToImport.Count == 0) break; + await NeoSystem.Blockchain.Ask(new Blockchain.Import + { + Blocks = blocksToImport, + Verify = verifyImport + }); + if (NeoSystem is null) return; + } + } + NeoSystem.StartNode(new ChannelsConfig + { + Tcp = new IPEndPoint(IPAddress.Any, Settings.Default.P2P.Port), + MinDesiredConnections = Settings.Default.P2P.MinDesiredConnections, + MaxConnections = Settings.Default.P2P.MaxConnections, + MaxConnectionsPerAddress = Settings.Default.P2P.MaxConnectionsPerAddress + }); + + if (Settings.Default.UnlockWallet.IsActive) + { + try + { + if (Settings.Default.UnlockWallet.Path is null) + { + throw new InvalidOperationException("UnlockWallet.Path must be defined"); + } + else if (Settings.Default.UnlockWallet.Password is null) + { + throw new InvalidOperationException("UnlockWallet.Password must be defined"); + } + + OpenWallet(Settings.Default.UnlockWallet.Path, Settings.Default.UnlockWallet.Password); + } + catch (FileNotFoundException) + { + ConsoleHelper.Warning($"wallet file \"{Settings.Default.UnlockWallet.Path}\" not found."); + } + catch (System.Security.Cryptography.CryptographicException) + { + ConsoleHelper.Error($"Failed to open file \"{Settings.Default.UnlockWallet.Path}\""); + } + catch (Exception ex) + { + ConsoleHelper.Error(ex.GetBaseException().Message); + } + } + + return; + + void DisplayError(string primaryMessage, string? secondaryMessage = null) + { + ConsoleHelper.Error(primaryMessage + Environment.NewLine + + (secondaryMessage != null ? secondaryMessage + Environment.NewLine : "") + + "Press any key to exit."); + Console.ReadKey(); + Environment.Exit(-1); + } + } + + public void Stop() + { + Dispose_Logger(); + Interlocked.Exchange(ref _neoSystem, null)?.Dispose(); + } + + private void WriteBlocks(uint start, uint count, string path, bool writeStart) + { + uint end = start + count - 1; + using FileStream fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None, 4096, FileOptions.WriteThrough); + if (fs.Length > 0) + { + byte[] buffer = new byte[sizeof(uint)]; + if (writeStart) + { + fs.Seek(sizeof(uint), SeekOrigin.Begin); + fs.Read(buffer, 0, buffer.Length); + start += BitConverter.ToUInt32(buffer, 0); + fs.Seek(sizeof(uint), SeekOrigin.Begin); + } + else + { + fs.Read(buffer, 0, buffer.Length); + start = BitConverter.ToUInt32(buffer, 0); + fs.Seek(0, SeekOrigin.Begin); + } + } + else + { + if (writeStart) + { + fs.Write(BitConverter.GetBytes(start), 0, sizeof(uint)); + } + } + if (start <= end) + fs.Write(BitConverter.GetBytes(count), 0, sizeof(uint)); + fs.Seek(0, SeekOrigin.End); + Console.WriteLine("Export block from " + start + " to " + end); + + using (var percent = new ConsolePercent(start, end)) + { + for (uint i = start; i <= end; i++) + { + Block block = NativeContract.Ledger.GetBlock(NeoSystem.StoreView, i); + byte[] array = block.ToArray(); + fs.Write(BitConverter.GetBytes(array.Length), 0, sizeof(int)); + fs.Write(array, 0, array.Length); + percent.Value = i; + } + } + } + + private static void WriteLineWithoutFlicker(string message = "", int maxWidth = 80) + { + if (message.Length > 0) Console.Write(message); + var spacesToErase = maxWidth - message.Length; + if (spacesToErase < 0) spacesToErase = 0; + Console.WriteLine(new string(' ', spacesToErase)); + } + + /// + /// Make and send transaction with script, sender + /// + /// script + /// sender + /// Max fee for running the script, in the unit of datoshi, 1 datoshi = 1e-8 GAS + private void SendTransaction(byte[] script, UInt160? account = null, long datoshi = TestModeGas) + { + if (NoWallet()) return; + + Signer[] signers = Array.Empty(); + var snapshot = NeoSystem.StoreView; + + if (account != null) + { + signers = CurrentWallet!.GetAccounts() + .Where(p => !p.Lock && !p.WatchOnly && p.ScriptHash == account && NativeContract.GAS.BalanceOf(snapshot, p.ScriptHash).Sign > 0) + .Select(p => new Signer { Account = p.ScriptHash, Scopes = WitnessScope.CalledByEntry }) + .ToArray(); + } + + try + { + Transaction tx = CurrentWallet!.MakeTransaction(snapshot, script, account, signers, maxGas: datoshi); + ConsoleHelper.Info("Invoking script with: ", $"'{Convert.ToBase64String(tx.Script.Span)}'"); + + using (ApplicationEngine engine = ApplicationEngine.Run(tx.Script, snapshot, container: tx, settings: NeoSystem.Settings, gas: datoshi)) + { + PrintExecutionOutput(engine, true); + if (engine.State == VMState.FAULT) return; + } + + if (!ConsoleHelper.ReadUserInput("Relay tx(no|yes)").IsYes()) + { + return; + } + + SignAndSendTx(NeoSystem.StoreView, tx); + } + catch (InvalidOperationException e) + { + ConsoleHelper.Error(GetExceptionMessage(e)); + } + } + + /// + /// Process "invoke" command + /// + /// Script hash + /// Operation + /// Result + /// Transaction + /// Contract parameters + /// Show result stack if it is true + /// Max fee for running the script, in the unit of datoshi, 1 datoshi = 1e-8 GAS + /// Return true if it was successful + private bool OnInvokeWithResult(UInt160 scriptHash, string operation, out StackItem result, IVerifiable? verifiable = null, JArray? contractParameters = null, bool showStack = true, long datoshi = TestModeGas) + { + List parameters = new(); + + if (contractParameters != null) + { + foreach (var contractParameter in contractParameters) + { + if (contractParameter is not null) + { + parameters.Add(ContractParameter.FromJson((JObject)contractParameter)); + } + } + } + + ContractState contract = NativeContract.ContractManagement.GetContract(NeoSystem.StoreView, scriptHash); + if (contract == null) + { + ConsoleHelper.Error("Contract does not exist."); + result = StackItem.Null; + return false; + } + else + { + if (contract.Manifest.Abi.GetMethod(operation, parameters.Count) == null) + { + ConsoleHelper.Error("This method does not not exist in this contract."); + result = StackItem.Null; + return false; + } + } + + byte[] script; + + using (ScriptBuilder scriptBuilder = new ScriptBuilder()) + { + scriptBuilder.EmitDynamicCall(scriptHash, operation, parameters.ToArray()); + script = scriptBuilder.ToArray(); + ConsoleHelper.Info("Invoking script with: ", $"'{script.ToBase64String()}'"); + } + + if (verifiable is Transaction tx) + { + tx.Script = script; + } + + using ApplicationEngine engine = ApplicationEngine.Run(script, NeoSystem.StoreView, container: verifiable, settings: NeoSystem.Settings, gas: datoshi); + PrintExecutionOutput(engine, showStack); + result = engine.State == VMState.FAULT ? StackItem.Null : engine.ResultStack.Peek(); + return engine.State != VMState.FAULT; + } + + private void PrintExecutionOutput(ApplicationEngine engine, bool showStack = true) + { + ConsoleHelper.Info("VM State: ", engine.State.ToString()); + ConsoleHelper.Info("Gas Consumed: ", new BigDecimal((BigInteger)engine.FeeConsumed, NativeContract.GAS.Decimals).ToString()); + + if (showStack) + ConsoleHelper.Info("Result Stack: ", new JArray(engine.ResultStack.Select(p => p.ToJson())).ToString()); + + if (engine.State == VMState.FAULT) + ConsoleHelper.Error(GetExceptionMessage(engine.FaultException)); + } + + static string GetExceptionMessage(Exception exception) + { + if (exception == null) return "Engine faulted."; + + if (exception.InnerException != null) + { + return GetExceptionMessage(exception.InnerException); + } + + return exception.Message; + } + + public UInt160 ResolveNeoNameServiceAddress(string domain) + { + if (Settings.Default.Contracts.NeoNameService == UInt160.Zero) + throw new Exception("Neo Name Service (NNS): is disabled on this network."); + + using var sb = new ScriptBuilder(); + sb.EmitDynamicCall(Settings.Default.Contracts.NeoNameService, "resolve", CallFlags.ReadOnly, domain, 16); + + using var appEng = ApplicationEngine.Run(sb.ToArray(), NeoSystem.StoreView, settings: NeoSystem.Settings); + if (appEng.State == VMState.HALT) + { + var data = appEng.ResultStack.Pop(); + if (data is ByteString) + { + try + { + var addressData = data.GetString(); + if (UInt160.TryParse(addressData, out var address)) + return address; + else + return addressData.ToScriptHash(NeoSystem.Settings.AddressVersion); + } + catch { } + } + else if (data is Null) + { + throw new Exception($"Neo Name Service (NNS): \"{domain}\" domain not found."); + } + throw new Exception("Neo Name Service (NNS): Record invalid address format."); + } + else + { + if (appEng.FaultException is not null) + { + throw new Exception($"Neo Name Service (NNS): \"{appEng.FaultException.Message}\"."); + } + } + throw new Exception($"Neo Name Service (NNS): \"{domain}\" domain not found."); + } + } +} diff --git a/src/Neo.CLI/CLI/ParseFunctionAttribute.cs b/src/Neo.CLI/CLI/ParseFunctionAttribute.cs new file mode 100644 index 0000000000..7dc92c661a --- /dev/null +++ b/src/Neo.CLI/CLI/ParseFunctionAttribute.cs @@ -0,0 +1,25 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ParseFunctionAttribute.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; + +namespace Neo.CLI +{ + internal class ParseFunctionAttribute : Attribute + { + public string Description { get; } + + public ParseFunctionAttribute(string description) + { + Description = description; + } + } +} diff --git a/src/Neo.CLI/Dockerfile b/src/Neo.CLI/Dockerfile new file mode 100644 index 0000000000..5863e9a74d --- /dev/null +++ b/src/Neo.CLI/Dockerfile @@ -0,0 +1,20 @@ +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS Build + +# Run this from the repository root folder +COPY src . +COPY NuGet.Config /Neo.CLI + +WORKDIR /Neo.CLI +RUN dotnet restore && dotnet publish -f net8.0 -c Release -o /app + +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/aspnet:8.0 AS Final +RUN apt-get update && apt-get install -y \ + screen \ + libleveldb-dev \ + sqlite3 +RUN rm -rf /var/lib/apt/lists/* + +WORKDIR /Neo.CLI +COPY --from=Build /app . + +ENTRYPOINT ["screen","-DmS","node","dotnet","neo-cli.dll","-r"] diff --git a/src/Neo.CLI/Extensions.cs b/src/Neo.CLI/Extensions.cs new file mode 100644 index 0000000000..b8331201be --- /dev/null +++ b/src/Neo.CLI/Extensions.cs @@ -0,0 +1,29 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Extensions.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Linq; +using System.Reflection; + +namespace Neo +{ + /// + /// Extension methods + /// + internal static class Extensions + { + public static string GetVersion(this Assembly assembly) + { + CustomAttributeData? attribute = assembly.CustomAttributes.FirstOrDefault(p => p.AttributeType == typeof(AssemblyInformationalVersionAttribute)); + if (attribute == null) return assembly.GetName().Version?.ToString(3) ?? string.Empty; + return (string)attribute.ConstructorArguments[0].Value!; + } + } +} diff --git a/src/Neo.CLI/Neo.CLI.csproj b/src/Neo.CLI/Neo.CLI.csproj new file mode 100644 index 0000000000..f3f319d358 --- /dev/null +++ b/src/Neo.CLI/Neo.CLI.csproj @@ -0,0 +1,41 @@ + + + + net8.0 + Neo.CLI + neo-cli + Exe + Neo.CLI + Neo + Neo.CLI + neo.ico + enable + ../../bin/$(AssemblyTitle) + + + + + + + + + + + + + PreserveNewest + PreserveNewest + + + + + + + + + + + + + + diff --git a/src/Neo.CLI/Program.cs b/src/Neo.CLI/Program.cs new file mode 100644 index 0000000000..b2a0f1b45c --- /dev/null +++ b/src/Neo.CLI/Program.cs @@ -0,0 +1,24 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Program.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.CLI; + +namespace Neo +{ + static class Program + { + static void Main(string[] args) + { + var mainService = new MainService(); + mainService.Run(args); + } + } +} diff --git a/src/Neo.CLI/Settings.cs b/src/Neo.CLI/Settings.cs new file mode 100644 index 0000000000..e831d15ebd --- /dev/null +++ b/src/Neo.CLI/Settings.cs @@ -0,0 +1,187 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Settings.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.Extensions.Configuration; +using Neo.Network.P2P; +using Neo.Persistence; +using System; +using System.Linq; +using System.Reflection; +using System.Threading; + +namespace Neo +{ + public class Settings + { + public LoggerSettings Logger { get; init; } + public StorageSettings Storage { get; init; } + public P2PSettings P2P { get; init; } + public UnlockWalletSettings UnlockWallet { get; init; } + public ContractsSettings Contracts { get; init; } + public PluginsSettings Plugins { get; init; } + + static Settings? s_default; + + static bool UpdateDefault(IConfiguration configuration) + { + var settings = new Settings(configuration.GetSection("ApplicationConfiguration")); + return null == Interlocked.CompareExchange(ref s_default, settings, null); + } + + public static bool Initialize(IConfiguration configuration) + { + return UpdateDefault(configuration); + } + + public static Settings Default + { + get + { + if (s_default == null) + { + var config = new ConfigurationBuilder().AddJsonFile("config.json", optional: true).Build(); + Initialize(config); + } + return Custom ?? s_default!; + } + } + + public static Settings? Custom { get; set; } + + public Settings(IConfigurationSection section) + { + Contracts = new(section.GetSection(nameof(Contracts))); + Logger = new(section.GetSection(nameof(Logger))); + Storage = new(section.GetSection(nameof(Storage))); + P2P = new(section.GetSection(nameof(P2P))); + UnlockWallet = new(section.GetSection(nameof(UnlockWallet))); + Plugins = new(section.GetSection(nameof(Plugins))); + } + + public Settings() + { + Logger = new LoggerSettings(); + Storage = new StorageSettings(); + P2P = new P2PSettings(); + UnlockWallet = new UnlockWalletSettings(); + Contracts = new ContractsSettings(); + Plugins = new PluginsSettings(); + } + } + + public class LoggerSettings + { + public string Path { get; init; } = string.Empty; + public bool ConsoleOutput { get; init; } + public bool Active { get; init; } + + public LoggerSettings(IConfigurationSection section) + { + Path = section.GetValue(nameof(Path), "Logs")!; + ConsoleOutput = section.GetValue(nameof(ConsoleOutput), false); + Active = section.GetValue(nameof(Active), false); + } + + public LoggerSettings() { } + } + + public class StorageSettings + { + public string Engine { get; init; } = nameof(MemoryStore); + public string Path { get; init; } = string.Empty; + + public StorageSettings(IConfigurationSection section) + { + Engine = section.GetValue(nameof(Engine), nameof(MemoryStore))!; + Path = section.GetValue(nameof(Path), string.Empty)!; + } + + public StorageSettings() { } + } + + public class P2PSettings + { + public ushort Port { get; } + public int MinDesiredConnections { get; } + public int MaxConnections { get; } + public int MaxConnectionsPerAddress { get; } + + public P2PSettings(IConfigurationSection section) + { + Port = section.GetValue(nameof(Port), 10333); + MinDesiredConnections = section.GetValue(nameof(MinDesiredConnections), Peer.DefaultMinDesiredConnections); + MaxConnections = section.GetValue(nameof(MaxConnections), Peer.DefaultMaxConnections); + MaxConnectionsPerAddress = section.GetValue(nameof(MaxConnectionsPerAddress), 3); + } + + public P2PSettings() { } + } + + public class UnlockWalletSettings + { + public string? Path { get; init; } = string.Empty; + public string? Password { get; init; } = string.Empty; + public bool IsActive { get; init; } = false; + + public UnlockWalletSettings(IConfigurationSection section) + { + if (section.Exists()) + { + Path = section.GetValue(nameof(Path), string.Empty)!; + Password = section.GetValue(nameof(Password), string.Empty)!; + IsActive = section.GetValue(nameof(IsActive), false); + } + } + + public UnlockWalletSettings() { } + } + + public class ContractsSettings + { + public UInt160 NeoNameService { get; init; } = UInt160.Zero; + + public ContractsSettings(IConfigurationSection section) + { + if (section.Exists()) + { + if (UInt160.TryParse(section.GetValue(nameof(NeoNameService), string.Empty), out var hash)) + { + NeoNameService = hash; + } + else + throw new ArgumentException("Neo Name Service (NNS): NeoNameService hash is invalid. Check your config.json.", nameof(NeoNameService)); + } + } + + public ContractsSettings() { } + } + + public class PluginsSettings + { + public Uri DownloadUrl { get; init; } = new("https://api.github.com/repos/neo-project/neo/releases"); + public bool Prerelease { get; init; } = false; + public Version Version { get; init; } = Assembly.GetExecutingAssembly().GetName().Version!; + + public PluginsSettings(IConfigurationSection section) + { + if (section.Exists()) + { + DownloadUrl = section.GetValue(nameof(DownloadUrl), DownloadUrl)!; +#if DEBUG + Prerelease = section.GetValue(nameof(Prerelease), Prerelease); + Version = section.GetValue(nameof(Version), Version)!; +#endif + } + } + + public PluginsSettings() { } + } +} diff --git a/src/Neo.CLI/config.fs.mainnet.json b/src/Neo.CLI/config.fs.mainnet.json new file mode 100644 index 0000000000..f629dc3aac --- /dev/null +++ b/src/Neo.CLI/config.fs.mainnet.json @@ -0,0 +1,63 @@ +{ + "ApplicationConfiguration": { + "Logger": { + "Path": "Logs", + "ConsoleOutput": false, + "Active": false + }, + "Storage": { + "Engine": "LevelDBStore", + "Path": "Data_LevelDB_{0}" + }, + "P2P": { + "Port": 40333, + "MinDesiredConnections": 10, + "MaxConnections": 40, + "MaxConnectionsPerAddress": 3 + }, + "UnlockWallet": { + "Path": "", + "Password": "", + "IsActive": false + }, + "Contracts": { + "NeoNameService": "0x7061fbd31562664b58f422c3dee4acfd70dba8af" + }, + "Plugins": { + "DownloadUrl": "https://api.github.com/repos/neo-project/neo/releases" + } + }, + "ProtocolConfiguration": { + "Network": 91414437, + "AddressVersion": 53, + "MillisecondsPerBlock": 15000, + "MaxTransactionsPerBlock": 512, + "MemoryPoolMaxTransactions": 50000, + "MaxTraceableBlocks": 2102400, + "InitialGasDistribution": 5200000000000000, + "ValidatorsCount": 7, + "Hardforks": { + "HF_Aspidochelone": 3000000, + "HF_Basilisk": 4500000, + "HF_Cockatrice": 5800000 + }, + "StandbyCommittee": [ + "026fa34ec057d74c2fdf1a18e336d0bd597ea401a0b2ad57340d5c220d09f44086", + "039a9db2a30942b1843db673aeb0d4fd6433f74cec1d879de6343cb9fcf7628fa4", + "0366d255e7ce23ea6f7f1e4bedf5cbafe598705b47e6ec213ef13b2f0819e8ab33", + "023f9cb7bbe154d529d5c719fdc39feaa831a43ae03d2a4280575b60f52fa7bc52", + "039ba959e0ab6dc616df8b803692f1c30ba9071b76b05535eb994bf5bbc402ad5f", + "035a2a18cddafa25ad353dea5e6730a1b9fcb4b918c4a0303c4387bb9c3b816adf", + "031f4d9c66f2ec348832c48fd3a16dfaeb59e85f557ae1e07f6696d0375c64f97b" + ], + "SeedList": [ + "morph1.fs.neo.org:40333", + "morph2.fs.neo.org:40333", + "morph3.fs.neo.org:40333", + "morph4.fs.neo.org:40333", + "morph5.fs.neo.org:40333", + "morph6.fs.neo.org:40333", + "morph7.fs.neo.org:40333" + ] + } +} diff --git a/src/Neo.CLI/config.fs.testnet.json b/src/Neo.CLI/config.fs.testnet.json new file mode 100644 index 0000000000..4e9468e2c6 --- /dev/null +++ b/src/Neo.CLI/config.fs.testnet.json @@ -0,0 +1,58 @@ +{ + "ApplicationConfiguration": { + "Logger": { + "Path": "Logs", + "ConsoleOutput": false, + "Active": false + }, + "Storage": { + "Engine": "LevelDBStore", + "Path": "Data_LevelDB_{0}" + }, + "P2P": { + "Port": 50333, + "MinDesiredConnections": 10, + "MaxConnections": 40, + "MaxConnectionsPerAddress": 3 + }, + "UnlockWallet": { + "Path": "", + "Password": "", + "IsActive": false + }, + "Contracts": { + "NeoNameService": "0xfb08ccf30ab534a871b7b092a49fe70c154ed678" + }, + "Plugins": { + "DownloadUrl": "https://api.github.com/repos/neo-project/neo/releases" + } + }, + "ProtocolConfiguration": { + "Network": 91466898, + "AddressVersion": 53, + "MillisecondsPerBlock": 15000, + "MaxTransactionsPerBlock": 512, + "MemoryPoolMaxTransactions": 50000, + "MaxTraceableBlocks": 2102400, + "InitialGasDistribution": 5200000000000000, + "ValidatorsCount": 7, + "StandbyCommittee": [ + "02082828ec6efc92e5e7790da851be72d2091a961c1ac9a1772acbf181ac56b831", + "02b2bcf7e09c0237ab6ef21808e6f7546329823bc6b43488335bd357aea443fabe", + "03577029a5072ebbab12d2495b59e2cf27afb37f9640c1c1354f1bdd221e6fb82d", + "03e6ea086e4b42fa5f0535179862db7eea7e44644e5e9608d6131aa48868c12cfc", + "0379328ab4907ea7c47f61e5c9d2c78c39dc9d1c4341ca496376070a0a5e20131e", + "02f8af6440dfe0e676ae2bb6727e5cc31a6f2459e29f48e85428862b7577dbc203", + "02e19c0634c85d35937699cdeaa10595ec2e18bfe86ba0494cf6c5c6861c66b97d" + ], + "SeedList": [ + "morph01.testnet.fs.neo.org:50333", + "morph02.testnet.fs.neo.org:50333", + "morph03.testnet.fs.neo.org:50333", + "morph04.testnet.fs.neo.org:50333", + "morph05.testnet.fs.neo.org:50333", + "morph06.testnet.fs.neo.org:50333", + "morph07.testnet.fs.neo.org:50333" + ] + } +} diff --git a/src/Neo.CLI/config.json b/src/Neo.CLI/config.json new file mode 100644 index 0000000000..821eb364f5 --- /dev/null +++ b/src/Neo.CLI/config.json @@ -0,0 +1,75 @@ +{ + "ApplicationConfiguration": { + "Logger": { + "Path": "Logs", + "ConsoleOutput": false, + "Active": false + }, + "Storage": { + "Engine": "LevelDBStore", + "Path": "Data_LevelDB_{0}" + }, + "P2P": { + "Port": 10333, + "MinDesiredConnections": 10, + "MaxConnections": 40, + "MaxConnectionsPerAddress": 3 + }, + "UnlockWallet": { + "Path": "", + "Password": "", + "IsActive": false + }, + "Contracts": { + "NeoNameService": "0x50ac1c37690cc2cfc594472833cf57505d5f46de" + }, + "Plugins": { + "DownloadUrl": "https://api.github.com/repos/neo-project/neo/releases" + } + }, + "ProtocolConfiguration": { + "Network": 860833102, + "AddressVersion": 53, + "MillisecondsPerBlock": 15000, + "MaxTransactionsPerBlock": 512, + "MemoryPoolMaxTransactions": 50000, + "MaxTraceableBlocks": 2102400, + "Hardforks": { + "HF_Aspidochelone": 1730000, + "HF_Basilisk": 4120000, + "HF_Cockatrice": 5450000 + }, + "InitialGasDistribution": 5200000000000000, + "ValidatorsCount": 7, + "StandbyCommittee": [ + "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", + "02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093", + "03b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a", + "02ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba554", + "024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d", + "02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e", + "02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70", + "023a36c72844610b4d34d1968662424011bf783ca9d984efa19a20babf5582f3fe", + "03708b860c1de5d87f5b151a12c2a99feebd2e8b315ee8e7cf8aa19692a9e18379", + "03c6aa6e12638b36e88adc1ccdceac4db9929575c3e03576c617c49cce7114a050", + "03204223f8c86b8cd5c89ef12e4f0dbb314172e9241e30c9ef2293790793537cf0", + "02a62c915cf19c7f19a50ec217e79fac2439bbaad658493de0c7d8ffa92ab0aa62", + "03409f31f0d66bdc2f70a9730b66fe186658f84a8018204db01c106edc36553cd0", + "0288342b141c30dc8ffcde0204929bb46aed5756b41ef4a56778d15ada8f0c6654", + "020f2887f41474cfeb11fd262e982051c1541418137c02a0f4961af911045de639", + "0222038884bbd1d8ff109ed3bdef3542e768eef76c1247aea8bc8171f532928c30", + "03d281b42002647f0113f36c7b8efb30db66078dfaaa9ab3ff76d043a98d512fde", + "02504acbc1f4b3bdad1d86d6e1a08603771db135a73e61c9d565ae06a1938cd2ad", + "0226933336f1b75baa42d42b71d9091508b638046d19abd67f4e119bf64a7cfb4d", + "03cdcea66032b82f5c30450e381e5295cae85c5e6943af716cc6b646352a6067dc", + "02cd5a5547119e24feaa7c2a0f37b8c9366216bab7054de0065c9be42084003c8a" + ], + "SeedList": [ + "seed1.neo.org:10333", + "seed2.neo.org:10333", + "seed3.neo.org:10333", + "seed4.neo.org:10333", + "seed5.neo.org:10333" + ] + } +} diff --git a/src/Neo.CLI/config.json.md b/src/Neo.CLI/config.json.md new file mode 100644 index 0000000000..b32b1f2f8f --- /dev/null +++ b/src/Neo.CLI/config.json.md @@ -0,0 +1,85 @@ +# README for Application and Protocol Configuration JSON File + +This README provides an explanation for each field in the JSON configuration file for a Neo node. + +## ApplicationConfiguration + +### Logger +- **Path**: Directory where log files are stored. Default is "Logs". +- **ConsoleOutput**: Boolean flag to enable or disable console output for logging. Default is `false`. +- **Active**: Boolean flag to activate or deactivate the logger. Default is `false`. + +### Storage +- **Engine**: Specifies the storage engine used by the node. Possible values are: + - `MemoryStore` + - `LevelDBStore` + - `RocksDBStore` +- **Path**: Path to the data storage directory. `{0}` is a placeholder for the network ID. + +### P2P +- **Port**: Port number for the P2P network. MainNet is `10333`, TestNet is `20333`. +- **MinDesiredConnections**: Minimum number of desired P2P connections. Default is `10`. +- **MaxConnections**: Maximum number of P2P connections. Default is `40`. +- **MaxConnectionsPerAddress**: Maximum number of connections allowed per address. Default is `3`. + +### UnlockWallet +- **Path**: Path to the wallet file. +- **Password**: Password for the wallet. +- **IsActive**: Boolean flag to activate or deactivate the wallet. Default is `false`. + +### Contracts +- **NeoNameService**: Script hash of the Neo Name Service contract. MainNet is `0x50ac1c37690cc2cfc594472833cf57505d5f46de`, TestNet is `0x50ac1c37690cc2cfc594472833cf57505d5f46de`. + +### Plugins +- **DownloadUrl**: URL to download plugins, typically from the Neo project's GitHub releases. Default is `https://api.github.com/repos/neo-project/neo/releases`. + +## ProtocolConfiguration + +### Network +- **Network**: Network ID for the Neo network. MainNet is `860833102`, TestNet is `894710606` + +### AddressVersion +- **AddressVersion**: Version byte used in Neo address generation. Default is `53`. + +### MillisecondsPerBlock +- **MillisecondsPerBlock**: Time interval between blocks in milliseconds. Default is `15000` (15 seconds). + +### MaxTransactionsPerBlock +- **MaxTransactionsPerBlock**: Maximum number of transactions allowed per block. Default is `512`. + +### MemoryPoolMaxTransactions +- **MemoryPoolMaxTransactions**: Maximum number of transactions that can be held in the memory pool. Default is `50000`. + +### MaxTraceableBlocks +- **MaxTraceableBlocks**: Maximum number of blocks that can be traced back. Default is `2102400`. + +### Hardforks +- **HF_Aspidochelone**: Block height for the Aspidochelone hard fork. MainNet is `1730000`, TestNet is `210000`. +- **HF_Basilisk**: Block height for the Basilisk hard fork. MainNet is `4120000`, TestNet is `2680000`. +- **HF_Cockatrice**: Block height for the Cockatrice hard fork. MainNet is `5450000`, TestNet is `3967000`. + +### InitialGasDistribution +- **InitialGasDistribution**: Total amount of GAS distributed initially. Default is `5,200,000,000,000,000 Datoshi` (`52,000,000 GAS`). + +### ValidatorsCount +- **ValidatorsCount**: Number of consensus validators. Default is `7`. + +### StandbyCommittee +- **StandbyCommittee**: List of public keys for the standby committee members. + +### SeedList +- **SeedList**: List of seed nodes with their addresses and ports. + - MainNet addresses are: + - `seed1.neo.org:10333` + - `seed2.neo.org:10333` + - `seed3.neo.org:10333` + - `seed4.neo.org:10333` + - `seed5.neo.org:10333` + - TestNet addresses are: + - `seed1t5.neo.org:20333` + - `seed2t5.neo.org:20333` + - `seed3t5.neo.org:20333` + - `seed4t5.neo.org:20333` + - `seed5t5.neo.org:20333` + +This configuration file is essential for setting up and running a Neo node, ensuring proper logging, storage, network connectivity, and consensus protocol parameters. diff --git a/src/Neo.CLI/config.mainnet.json b/src/Neo.CLI/config.mainnet.json new file mode 100644 index 0000000000..821eb364f5 --- /dev/null +++ b/src/Neo.CLI/config.mainnet.json @@ -0,0 +1,75 @@ +{ + "ApplicationConfiguration": { + "Logger": { + "Path": "Logs", + "ConsoleOutput": false, + "Active": false + }, + "Storage": { + "Engine": "LevelDBStore", + "Path": "Data_LevelDB_{0}" + }, + "P2P": { + "Port": 10333, + "MinDesiredConnections": 10, + "MaxConnections": 40, + "MaxConnectionsPerAddress": 3 + }, + "UnlockWallet": { + "Path": "", + "Password": "", + "IsActive": false + }, + "Contracts": { + "NeoNameService": "0x50ac1c37690cc2cfc594472833cf57505d5f46de" + }, + "Plugins": { + "DownloadUrl": "https://api.github.com/repos/neo-project/neo/releases" + } + }, + "ProtocolConfiguration": { + "Network": 860833102, + "AddressVersion": 53, + "MillisecondsPerBlock": 15000, + "MaxTransactionsPerBlock": 512, + "MemoryPoolMaxTransactions": 50000, + "MaxTraceableBlocks": 2102400, + "Hardforks": { + "HF_Aspidochelone": 1730000, + "HF_Basilisk": 4120000, + "HF_Cockatrice": 5450000 + }, + "InitialGasDistribution": 5200000000000000, + "ValidatorsCount": 7, + "StandbyCommittee": [ + "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", + "02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093", + "03b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a", + "02ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba554", + "024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d", + "02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e", + "02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70", + "023a36c72844610b4d34d1968662424011bf783ca9d984efa19a20babf5582f3fe", + "03708b860c1de5d87f5b151a12c2a99feebd2e8b315ee8e7cf8aa19692a9e18379", + "03c6aa6e12638b36e88adc1ccdceac4db9929575c3e03576c617c49cce7114a050", + "03204223f8c86b8cd5c89ef12e4f0dbb314172e9241e30c9ef2293790793537cf0", + "02a62c915cf19c7f19a50ec217e79fac2439bbaad658493de0c7d8ffa92ab0aa62", + "03409f31f0d66bdc2f70a9730b66fe186658f84a8018204db01c106edc36553cd0", + "0288342b141c30dc8ffcde0204929bb46aed5756b41ef4a56778d15ada8f0c6654", + "020f2887f41474cfeb11fd262e982051c1541418137c02a0f4961af911045de639", + "0222038884bbd1d8ff109ed3bdef3542e768eef76c1247aea8bc8171f532928c30", + "03d281b42002647f0113f36c7b8efb30db66078dfaaa9ab3ff76d043a98d512fde", + "02504acbc1f4b3bdad1d86d6e1a08603771db135a73e61c9d565ae06a1938cd2ad", + "0226933336f1b75baa42d42b71d9091508b638046d19abd67f4e119bf64a7cfb4d", + "03cdcea66032b82f5c30450e381e5295cae85c5e6943af716cc6b646352a6067dc", + "02cd5a5547119e24feaa7c2a0f37b8c9366216bab7054de0065c9be42084003c8a" + ], + "SeedList": [ + "seed1.neo.org:10333", + "seed2.neo.org:10333", + "seed3.neo.org:10333", + "seed4.neo.org:10333", + "seed5.neo.org:10333" + ] + } +} diff --git a/src/Neo.CLI/config.testnet.json b/src/Neo.CLI/config.testnet.json new file mode 100644 index 0000000000..ee28108c3e --- /dev/null +++ b/src/Neo.CLI/config.testnet.json @@ -0,0 +1,75 @@ +{ + "ApplicationConfiguration": { + "Logger": { + "Path": "Logs", + "ConsoleOutput": false, + "Active": false + }, + "Storage": { + "Engine": "LevelDBStore", + "Path": "Data_LevelDB_{0}" + }, + "P2P": { + "Port": 20333, + "MinDesiredConnections": 10, + "MaxConnections": 40, + "MaxConnectionsPerAddress": 3 + }, + "UnlockWallet": { + "Path": "", + "Password": "", + "IsActive": false + }, + "Contracts": { + "NeoNameService": "0x50ac1c37690cc2cfc594472833cf57505d5f46de" + }, + "Plugins": { + "DownloadUrl": "https://api.github.com/repos/neo-project/neo/releases" + } + }, + "ProtocolConfiguration": { + "Network": 894710606, + "AddressVersion": 53, + "MillisecondsPerBlock": 15000, + "MaxTransactionsPerBlock": 5000, + "MemoryPoolMaxTransactions": 50000, + "MaxTraceableBlocks": 2102400, + "Hardforks": { + "HF_Aspidochelone": 210000, + "HF_Basilisk": 2680000, + "HF_Cockatrice": 3967000 + }, + "InitialGasDistribution": 5200000000000000, + "ValidatorsCount": 7, + "StandbyCommittee": [ + "023e9b32ea89b94d066e649b124fd50e396ee91369e8e2a6ae1b11c170d022256d", + "03009b7540e10f2562e5fd8fac9eaec25166a58b26e412348ff5a86927bfac22a2", + "02ba2c70f5996f357a43198705859fae2cfea13e1172962800772b3d588a9d4abd", + "03408dcd416396f64783ac587ea1e1593c57d9fea880c8a6a1920e92a259477806", + "02a7834be9b32e2981d157cb5bbd3acb42cfd11ea5c3b10224d7a44e98c5910f1b", + "0214baf0ceea3a66f17e7e1e839ea25fd8bed6cd82e6bb6e68250189065f44ff01", + "030205e9cefaea5a1dfc580af20c8d5aa2468bb0148f1a5e4605fc622c80e604ba", + "025831cee3708e87d78211bec0d1bfee9f4c85ae784762f042e7f31c0d40c329b8", + "02cf9dc6e85d581480d91e88e8cbeaa0c153a046e89ded08b4cefd851e1d7325b5", + "03840415b0a0fcf066bcc3dc92d8349ebd33a6ab1402ef649bae00e5d9f5840828", + "026328aae34f149853430f526ecaa9cf9c8d78a4ea82d08bdf63dd03c4d0693be6", + "02c69a8d084ee7319cfecf5161ff257aa2d1f53e79bf6c6f164cff5d94675c38b3", + "0207da870cedb777fceff948641021714ec815110ca111ccc7a54c168e065bda70", + "035056669864feea401d8c31e447fb82dd29f342a9476cfd449584ce2a6165e4d7", + "0370c75c54445565df62cfe2e76fbec4ba00d1298867972213530cae6d418da636", + "03957af9e77282ae3263544b7b2458903624adc3f5dee303957cb6570524a5f254", + "03d84d22b8753cf225d263a3a782a4e16ca72ef323cfde04977c74f14873ab1e4c", + "02147c1b1d5728e1954958daff2f88ee2fa50a06890a8a9db3fa9e972b66ae559f", + "03c609bea5a4825908027e4ab217e7efc06e311f19ecad9d417089f14927a173d5", + "0231edee3978d46c335e851c76059166eb8878516f459e085c0dd092f0f1d51c21", + "03184b018d6b2bc093e535519732b3fd3f7551c8cffaf4621dd5a0b89482ca66c9" + ], + "SeedList": [ + "seed1t5.neo.org:20333", + "seed2t5.neo.org:20333", + "seed3t5.neo.org:20333", + "seed4t5.neo.org:20333", + "seed5t5.neo.org:20333" + ] + } +} diff --git a/src/Neo.CLI/neo.ico b/src/Neo.CLI/neo.ico new file mode 100644 index 0000000000..403aa7f376 Binary files /dev/null and b/src/Neo.CLI/neo.ico differ diff --git a/src/Neo.ConsoleService/CommandQuoteToken.cs b/src/Neo.ConsoleService/CommandQuoteToken.cs new file mode 100644 index 0000000000..865b7560d0 --- /dev/null +++ b/src/Neo.ConsoleService/CommandQuoteToken.cs @@ -0,0 +1,54 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// CommandQuoteToken.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Diagnostics; + +namespace Neo.ConsoleService +{ + [DebuggerDisplay("Value={Value}, Value={Value}")] + internal class CommandQuoteToken : CommandToken + { + /// + /// Constructor + /// + /// Offset + /// Value + public CommandQuoteToken(int offset, char value) : base(CommandTokenType.Quote, offset) + { + if (value != '\'' && value != '"') + { + throw new ArgumentException("Not valid quote"); + } + + Value = value.ToString(); + } + + /// + /// Parse command line quotes + /// + /// Command line + /// Index + /// CommandQuoteToken + internal static CommandQuoteToken Parse(string commandLine, ref int index) + { + var c = commandLine[index]; + + if (c == '\'' || c == '"') + { + index++; + return new CommandQuoteToken(index - 1, c); + } + + throw new ArgumentException("No quote found"); + } + } +} diff --git a/src/Neo.ConsoleService/CommandSpaceToken.cs b/src/Neo.ConsoleService/CommandSpaceToken.cs new file mode 100644 index 0000000000..87080d1983 --- /dev/null +++ b/src/Neo.ConsoleService/CommandSpaceToken.cs @@ -0,0 +1,65 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// CommandSpaceToken.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Diagnostics; + +namespace Neo.ConsoleService +{ + [DebuggerDisplay("Value={Value}, Count={Count}")] + internal class CommandSpaceToken : CommandToken + { + /// + /// Count + /// + public int Count { get; } + + /// + /// Constructor + /// + /// Offset + /// Count + public CommandSpaceToken(int offset, int count) : base(CommandTokenType.Space, offset) + { + Value = "".PadLeft(count, ' '); + Count = count; + } + + /// + /// Parse command line spaces + /// + /// Command line + /// Index + /// CommandSpaceToken + internal static CommandSpaceToken Parse(string commandLine, ref int index) + { + int offset = index; + int count = 0; + + for (int ix = index, max = commandLine.Length; ix < max; ix++) + { + if (commandLine[ix] == ' ') + { + count++; + } + else + { + break; + } + } + + if (count == 0) throw new ArgumentException("No spaces found"); + + index += count; + return new CommandSpaceToken(offset, count); + } + } +} diff --git a/src/Neo.ConsoleService/CommandStringToken.cs b/src/Neo.ConsoleService/CommandStringToken.cs new file mode 100644 index 0000000000..49a5702756 --- /dev/null +++ b/src/Neo.ConsoleService/CommandStringToken.cs @@ -0,0 +1,91 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// CommandStringToken.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Diagnostics; + +namespace Neo.ConsoleService +{ + [DebuggerDisplay("Value={Value}, RequireQuotes={RequireQuotes}")] + internal class CommandStringToken : CommandToken + { + /// + /// Require quotes + /// + public bool RequireQuotes { get; } + + /// + /// Constructor + /// + /// Offset + /// Value + public CommandStringToken(int offset, string value) : base(CommandTokenType.String, offset) + { + Value = value; + RequireQuotes = value.IndexOfAny(new char[] { '\'', '"' }) != -1; + } + + /// + /// Parse command line spaces + /// + /// Command line + /// Index + /// Quote (could be null) + /// CommandSpaceToken + internal static CommandStringToken Parse(string commandLine, ref int index, CommandQuoteToken? quote) + { + int end; + int offset = index; + + if (quote != null) + { + var ix = index; + + do + { + end = commandLine.IndexOf(quote.Value[0], ix + 1); + + if (end == -1) + { + throw new ArgumentException("String not closed"); + } + + if (IsScaped(commandLine, end - 1)) + { + ix = end; + end = -1; + } + } + while (end < 0); + } + else + { + end = commandLine.IndexOf(' ', index + 1); + } + + if (end == -1) + { + end = commandLine.Length; + } + + var ret = new CommandStringToken(offset, commandLine.Substring(index, end - index)); + index += end - index; + return ret; + } + + private static bool IsScaped(string commandLine, int index) + { + // TODO: Scape the scape + + return (commandLine[index] == '\\'); + } + } +} diff --git a/src/Neo.ConsoleService/CommandToken.cs b/src/Neo.ConsoleService/CommandToken.cs new file mode 100644 index 0000000000..10e8336ecc --- /dev/null +++ b/src/Neo.ConsoleService/CommandToken.cs @@ -0,0 +1,229 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// CommandToken.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Neo.ConsoleService +{ + internal abstract class CommandToken + { + /// + /// Offset + /// + public int Offset { get; } + + /// + /// Type + /// + public CommandTokenType Type { get; } + + /// + /// Value + /// + public string Value { get; protected init; } = string.Empty; + + /// + /// Constructor + /// + /// Type + /// Offset + protected CommandToken(CommandTokenType type, int offset) + { + Type = type; + Offset = offset; + } + + /// + /// Parse command line + /// + /// Command line + /// + public static IEnumerable Parse(string commandLine) + { + CommandToken? lastToken = null; + + for (int index = 0, count = commandLine.Length; index < count;) + { + switch (commandLine[index]) + { + case ' ': + { + lastToken = CommandSpaceToken.Parse(commandLine, ref index); + yield return lastToken; + break; + } + case '"': + case '\'': + { + // "'" + if (lastToken is CommandQuoteToken quote && quote.Value[0] != commandLine[index]) + { + goto default; + } + + lastToken = CommandQuoteToken.Parse(commandLine, ref index); + yield return lastToken; + break; + } + default: + { + lastToken = CommandStringToken.Parse(commandLine, ref index, + lastToken is CommandQuoteToken quote ? quote : null); + + if (lastToken is not null) + { + yield return lastToken; + } + break; + } + } + } + } + + /// + /// Create string arguments + /// + /// Tokens + /// Remove escape + /// Arguments + public static string[] ToArguments(IEnumerable tokens, bool removeEscape = true) + { + var list = new List(); + + CommandToken? lastToken = null; + + foreach (var token in tokens) + { + if (token is CommandStringToken str) + { + if (removeEscape && lastToken is CommandQuoteToken quote) + { + // Remove escape + + list.Add(str.Value.Replace("\\" + quote.Value, quote.Value)); + } + else + { + list.Add(str.Value); + } + } + + lastToken = token; + } + + return list.ToArray(); + } + + /// + /// Create a string from token list + /// + /// Tokens + /// String + public static string ToString(IEnumerable tokens) + { + var sb = new StringBuilder(); + + foreach (var token in tokens) + { + sb.Append(token.Value); + } + + return sb.ToString(); + } + + /// + /// Trim + /// + /// Args + public static void Trim(List args) + { + // Trim start + + while (args.Count > 0 && args[0].Type == CommandTokenType.Space) + { + args.RemoveAt(0); + } + + // Trim end + + while (args.Count > 0 && args[^1].Type == CommandTokenType.Space) + { + args.RemoveAt(args.Count - 1); + } + } + + /// + /// Read String + /// + /// Args + /// Consume all if not quoted + /// String + public static string? ReadString(List args, bool consumeAll) + { + Trim(args); + + var quoted = false; + + if (args.Count > 0 && args[0].Type == CommandTokenType.Quote) + { + quoted = true; + args.RemoveAt(0); + } + else + { + if (consumeAll) + { + // Return all if it's not quoted + + var ret = ToString(args); + args.Clear(); + + return ret; + } + } + + if (args.Count > 0) + { + switch (args[0]) + { + case CommandQuoteToken _: + { + if (quoted) + { + args.RemoveAt(0); + return ""; + } + + throw new ArgumentException(); + } + case CommandSpaceToken _: throw new ArgumentException(); + case CommandStringToken str: + { + args.RemoveAt(0); + + if (quoted && args.Count > 0 && args[0].Type == CommandTokenType.Quote) + { + // Remove last quote + + args.RemoveAt(0); + } + + return str.Value; + } + } + } + + return null; + } + } +} diff --git a/src/Neo.ConsoleService/CommandTokenType.cs b/src/Neo.ConsoleService/CommandTokenType.cs new file mode 100644 index 0000000000..2e522c000a --- /dev/null +++ b/src/Neo.ConsoleService/CommandTokenType.cs @@ -0,0 +1,20 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// CommandTokenType.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.ConsoleService +{ + internal enum CommandTokenType : byte + { + String, + Space, + Quote, + } +} diff --git a/src/Neo.ConsoleService/ConsoleColorSet.cs b/src/Neo.ConsoleService/ConsoleColorSet.cs new file mode 100644 index 0000000000..8e5c7f7b45 --- /dev/null +++ b/src/Neo.ConsoleService/ConsoleColorSet.cs @@ -0,0 +1,52 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ConsoleColorSet.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; + +namespace Neo.ConsoleService +{ + public class ConsoleColorSet + { + public ConsoleColor Foreground; + public ConsoleColor Background; + + /// + /// Create a new color set with the current console colors + /// + public ConsoleColorSet() : this(Console.ForegroundColor, Console.BackgroundColor) { } + + /// + /// Create a new color set + /// + /// Foreground color + public ConsoleColorSet(ConsoleColor foreground) : this(foreground, Console.BackgroundColor) { } + + /// + /// Create a new color set + /// + /// Foreground color + /// Background color + public ConsoleColorSet(ConsoleColor foreground, ConsoleColor background) + { + Foreground = foreground; + Background = background; + } + + /// + /// Apply the current set + /// + public void Apply() + { + Console.ForegroundColor = Foreground; + Console.BackgroundColor = Background; + } + } +} diff --git a/src/Neo.ConsoleService/ConsoleCommandAttribute.cs b/src/Neo.ConsoleService/ConsoleCommandAttribute.cs new file mode 100644 index 0000000000..c7831d617d --- /dev/null +++ b/src/Neo.ConsoleService/ConsoleCommandAttribute.cs @@ -0,0 +1,46 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ConsoleCommandAttribute.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Diagnostics; +using System.Linq; + +namespace Neo.ConsoleService +{ + [DebuggerDisplay("Verbs={string.Join(' ',Verbs)}")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public class ConsoleCommandAttribute : Attribute + { + /// + /// Verbs + /// + public string[] Verbs { get; } + + /// + /// Category + /// + public string Category { get; set; } = string.Empty; + + /// + /// Description + /// + public string Description { get; set; } = string.Empty; + + /// + /// Constructor + /// + /// Verbs + public ConsoleCommandAttribute(string verbs) + { + Verbs = verbs.Split(' ', StringSplitOptions.RemoveEmptyEntries).Select(u => u.ToLowerInvariant()).ToArray(); + } + } +} diff --git a/src/Neo.ConsoleService/ConsoleCommandMethod.cs b/src/Neo.ConsoleService/ConsoleCommandMethod.cs new file mode 100644 index 0000000000..3455d21bb4 --- /dev/null +++ b/src/Neo.ConsoleService/ConsoleCommandMethod.cs @@ -0,0 +1,121 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ConsoleCommandMethod.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; + +namespace Neo.ConsoleService +{ + [DebuggerDisplay("Key={Key}")] + internal class ConsoleCommandMethod + { + /// + /// Verbs + /// + public string[] Verbs { get; } + + /// + /// Key + /// + public string Key => string.Join(' ', Verbs); + + /// + /// Help category + /// + public string HelpCategory { get; set; } + + /// + /// Help message + /// + public string HelpMessage { get; set; } + + /// + /// Instance + /// + public object Instance { get; } + + /// + /// Method + /// + public MethodInfo Method { get; } + + /// + /// Set instance command + /// + /// Instance + /// Method + /// Attribute + public ConsoleCommandMethod(object instance, MethodInfo method, ConsoleCommandAttribute attribute) + { + Method = method; + Instance = instance; + Verbs = attribute.Verbs; + HelpCategory = attribute.Category; + HelpMessage = attribute.Description; + } + + /// + /// Is this command + /// + /// Tokens + /// Consumed Arguments + /// True if is this command + public bool IsThisCommand(CommandToken[] tokens, out int consumedArgs) + { + int checks = Verbs.Length; + bool quoted = false; + var tokenList = new List(tokens); + + while (checks > 0 && tokenList.Count > 0) + { + switch (tokenList[0]) + { + case CommandSpaceToken _: + { + tokenList.RemoveAt(0); + break; + } + case CommandQuoteToken _: + { + quoted = !quoted; + tokenList.RemoveAt(0); + break; + } + case CommandStringToken str: + { + if (Verbs[^checks] != str.Value.ToLowerInvariant()) + { + consumedArgs = 0; + return false; + } + + checks--; + tokenList.RemoveAt(0); + break; + } + } + } + + if (quoted && tokenList.Count > 0 && tokenList[0].Type == CommandTokenType.Quote) + { + tokenList.RemoveAt(0); + } + + // Trim start + + while (tokenList.Count > 0 && tokenList[0].Type == CommandTokenType.Space) tokenList.RemoveAt(0); + + consumedArgs = tokens.Length - tokenList.Count; + return checks == 0; + } + } +} diff --git a/src/Neo.ConsoleService/ConsoleHelper.cs b/src/Neo.ConsoleService/ConsoleHelper.cs new file mode 100644 index 0000000000..78f170a3af --- /dev/null +++ b/src/Neo.ConsoleService/ConsoleHelper.cs @@ -0,0 +1,163 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ConsoleHelper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Security; +using System.Text; + +namespace Neo.ConsoleService +{ + public static class ConsoleHelper + { + private static readonly ConsoleColorSet InfoColor = new(ConsoleColor.Cyan); + private static readonly ConsoleColorSet WarningColor = new(ConsoleColor.Yellow); + private static readonly ConsoleColorSet ErrorColor = new(ConsoleColor.Red); + + public static bool ReadingPassword { get; private set; } = false; + + /// + /// Info handles message in the format of "[tag]:[message]", + /// avoid using Info if the `tag` is too long + /// + /// The log message in pairs of (tag, message) + public static void Info(params string[] values) + { + var currentColor = new ConsoleColorSet(); + + for (int i = 0; i < values.Length; i++) + { + if (i % 2 == 0) + InfoColor.Apply(); + else + currentColor.Apply(); + Console.Write(values[i]); + } + currentColor.Apply(); + Console.WriteLine(); + } + + /// + /// Use warning if something unexpected happens + /// or the execution result is not correct. + /// Also use warning if you just want to remind + /// user of doing something. + /// + /// Warning message + public static void Warning(string msg) + { + Log("Warning", WarningColor, msg); + } + + /// + /// Use Error if the verification or input format check fails + /// or exception that breaks the execution of interactive + /// command throws. + /// + /// Error message + public static void Error(string msg) + { + Log("Error", ErrorColor, msg); + } + + private static void Log(string tag, ConsoleColorSet colorSet, string msg) + { + var currentColor = new ConsoleColorSet(); + + colorSet.Apply(); + Console.Write($"{tag}: "); + currentColor.Apply(); + Console.WriteLine(msg); + } + + public static string ReadUserInput(string prompt, bool password = false) + { + const string t = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"; + var sb = new StringBuilder(); + + if (!string.IsNullOrEmpty(prompt)) + { + Console.Write(prompt + ": "); + } + + if (password) ReadingPassword = true; + var prevForeground = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Yellow; + + if (Console.IsInputRedirected) + { + // neo-gui Console require it + sb.Append(Console.ReadLine()); + } + else + { + ConsoleKeyInfo key; + do + { + key = Console.ReadKey(true); + + if (t.IndexOf(key.KeyChar) != -1) + { + sb.Append(key.KeyChar); + Console.Write(password ? '*' : key.KeyChar); + } + else if (key.Key == ConsoleKey.Backspace && sb.Length > 0) + { + sb.Length--; + Console.Write("\b \b"); + } + } while (key.Key != ConsoleKey.Enter); + } + + Console.ForegroundColor = prevForeground; + if (password) ReadingPassword = false; + Console.WriteLine(); + return sb.ToString(); + } + + public static SecureString ReadSecureString(string prompt) + { + const string t = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"; + SecureString securePwd = new SecureString(); + ConsoleKeyInfo key; + + if (!string.IsNullOrEmpty(prompt)) + { + Console.Write(prompt + ": "); + } + + ReadingPassword = true; + Console.ForegroundColor = ConsoleColor.Yellow; + + do + { + key = Console.ReadKey(true); + if (t.IndexOf(key.KeyChar) != -1) + { + securePwd.AppendChar(key.KeyChar); + Console.Write('*'); + } + else if (key.Key == ConsoleKey.Backspace && securePwd.Length > 0) + { + securePwd.RemoveAt(securePwd.Length - 1); + Console.Write(key.KeyChar); + Console.Write(' '); + Console.Write(key.KeyChar); + } + } while (key.Key != ConsoleKey.Enter); + + Console.ForegroundColor = ConsoleColor.White; + ReadingPassword = false; + Console.WriteLine(); + securePwd.MakeReadOnly(); + return securePwd; + } + } +} diff --git a/src/Neo.ConsoleService/ConsoleServiceBase.cs b/src/Neo.ConsoleService/ConsoleServiceBase.cs new file mode 100644 index 0000000000..1da87354fb --- /dev/null +++ b/src/Neo.ConsoleService/ConsoleServiceBase.cs @@ -0,0 +1,562 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ConsoleServiceBase.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Runtime.Loader; +using System.ServiceProcess; +using System.Threading; +using System.Threading.Tasks; + +namespace Neo.ConsoleService +{ + public abstract class ConsoleServiceBase + { + protected virtual string? Depends => null; + protected virtual string Prompt => "service"; + + public abstract string ServiceName { get; } + + protected bool ShowPrompt { get; set; } = true; + + private bool _running; + private readonly CancellationTokenSource _shutdownTokenSource = new(); + private readonly CountdownEvent _shutdownAcknowledged = new(1); + private readonly Dictionary> _verbs = new(); + private readonly Dictionary _instances = new(); + private readonly Dictionary, bool, object>> _handlers = new(); + + private bool OnCommand(string commandLine) + { + if (string.IsNullOrEmpty(commandLine)) + { + return true; + } + + string? possibleHelp = null; + var commandArgs = CommandToken.Parse(commandLine).ToArray(); + var availableCommands = new List<(ConsoleCommandMethod Command, object?[] Arguments)>(); + + foreach (var entries in _verbs.Values) + { + foreach (var command in entries) + { + if (command.IsThisCommand(commandArgs, out var consumedArgs)) + { + var arguments = new List(); + var args = commandArgs.Skip(consumedArgs).ToList(); + + CommandSpaceToken.Trim(args); + + try + { + var parameters = command.Method.GetParameters(); + + foreach (var arg in parameters) + { + // Parse argument + + if (TryProcessValue(arg.ParameterType, args, arg == parameters.Last(), out var value)) + { + arguments.Add(value); + } + else + { + if (arg.HasDefaultValue) + { + arguments.Add(arg.DefaultValue); + } + else + { + throw new ArgumentException(arg.Name); + } + } + } + + availableCommands.Add((command, arguments.ToArray())); + } + catch (Exception ex) + { + // Skip parse errors + possibleHelp = command.Key; + ConsoleHelper.Error($"{ex.InnerException?.Message ?? ex.Message}"); + } + } + } + } + + switch (availableCommands.Count) + { + case 0: + { + if (!string.IsNullOrEmpty(possibleHelp)) + { + OnHelpCommand(possibleHelp); + return true; + } + + return false; + } + case 1: + { + var (command, arguments) = availableCommands[0]; + object? result = command.Method.Invoke(command.Instance, arguments); + if (result is Task task) task.Wait(); + return true; + } + default: + { + // Show Ambiguous call + + throw new ArgumentException("Ambiguous calls for: " + string.Join(',', availableCommands.Select(u => u.Command.Key).Distinct())); + } + } + } + + private bool TryProcessValue(Type parameterType, List args, bool canConsumeAll, out object? value) + { + if (args.Count > 0) + { + if (_handlers.TryGetValue(parameterType, out var handler)) + { + value = handler(args, canConsumeAll); + return true; + } + + if (parameterType.IsEnum) + { + var arg = CommandToken.ReadString(args, canConsumeAll); + if (arg is not null) + { + value = Enum.Parse(parameterType, arg.Trim(), true); + return true; + } + } + } + + value = null; + return false; + } + + #region Commands + + /// + /// Process "help" command + /// + [ConsoleCommand("help", Category = "Base Commands")] + protected void OnHelpCommand(string key) + { + var withHelp = new List(); + + // Try to find a plugin with this name + + if (_instances.TryGetValue(key.Trim().ToLowerInvariant(), out var instance)) + { + // Filter only the help of this plugin + + key = ""; + foreach (var commands in _verbs.Values.Select(u => u)) + { + withHelp.AddRange + ( + commands.Where(u => !string.IsNullOrEmpty(u.HelpCategory) && u.Instance == instance) + ); + } + } + else + { + // Fetch commands + + foreach (var commands in _verbs.Values.Select(u => u)) + { + withHelp.AddRange(commands.Where(u => !string.IsNullOrEmpty(u.HelpCategory))); + } + } + + // Sort and show + + withHelp.Sort((a, b) => + { + var cate = string.Compare(a.HelpCategory, b.HelpCategory, StringComparison.Ordinal); + if (cate == 0) + { + cate = string.Compare(a.Key, b.Key, StringComparison.Ordinal); + } + return cate; + }); + + if (string.IsNullOrEmpty(key) || key.Equals("help", StringComparison.InvariantCultureIgnoreCase)) + { + string? last = null; + foreach (var command in withHelp) + { + if (last != command.HelpCategory) + { + Console.WriteLine($"{command.HelpCategory}:"); + last = command.HelpCategory; + } + + Console.Write($"\t{command.Key}"); + Console.WriteLine(" " + string.Join(' ', + command.Method.GetParameters() + .Select(u => u.HasDefaultValue ? $"[{u.Name}={(u.DefaultValue == null ? "null" : u.DefaultValue.ToString())}]" : $"<{u.Name}>")) + ); + } + } + else + { + // Show help for this specific command + + string? last = null; + string? lastKey = null; + bool found = false; + + foreach (var command in withHelp.Where(u => u.Key == key)) + { + found = true; + + if (last != command.HelpMessage) + { + Console.WriteLine($"{command.HelpMessage}"); + last = command.HelpMessage; + } + + if (lastKey != command.Key) + { + Console.WriteLine("You can call this command like this:"); + lastKey = command.Key; + } + + Console.Write($"\t{command.Key}"); + Console.WriteLine(" " + string.Join(' ', + command.Method.GetParameters() + .Select(u => u.HasDefaultValue ? $"[{u.Name}={u.DefaultValue?.ToString() ?? "null"}]" : $"<{u.Name}>")) + ); + } + + if (!found) + { + throw new ArgumentException("Command not found."); + } + } + } + + /// + /// Process "clear" command + /// + [ConsoleCommand("clear", Category = "Base Commands", Description = "Clear is used in order to clean the console output.")] + protected void OnClear() + { + Console.Clear(); + } + + /// + /// Process "version" command + /// + [ConsoleCommand("version", Category = "Base Commands", Description = "Show the current version.")] + protected void OnVersion() + { + Console.WriteLine(Assembly.GetEntryAssembly()!.GetName().Version); + } + + /// + /// Process "exit" command + /// + [ConsoleCommand("exit", Category = "Base Commands", Description = "Exit the node.")] + protected void OnExit() + { + _running = false; + } + + #endregion + + public virtual bool OnStart(string[] args) + { + // Register sigterm event handler + AssemblyLoadContext.Default.Unloading += SigTermEventHandler; + // Register sigint event handler + Console.CancelKeyPress += CancelHandler; + return true; + } + + public virtual void OnStop() + { + _shutdownAcknowledged.Signal(); + } + + private void TriggerGracefulShutdown() + { + if (!_running) return; + _running = false; + _shutdownTokenSource.Cancel(); + // Wait for us to have triggered shutdown. + _shutdownAcknowledged.Wait(); + } + + private void SigTermEventHandler(AssemblyLoadContext obj) + { + TriggerGracefulShutdown(); + } + + private void CancelHandler(object? sender, ConsoleCancelEventArgs e) + { + e.Cancel = true; + TriggerGracefulShutdown(); + } + + /// + /// Constructor + /// + protected ConsoleServiceBase() + { + // Register self commands + + RegisterCommandHandler((args, canConsumeAll) => CommandToken.ReadString(args, canConsumeAll) ?? ""); + + RegisterCommandHandler((args, canConsumeAll) => + { + if (canConsumeAll) + { + var ret = CommandToken.ToString(args); + args.Clear(); + return ret.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries); + } + + return (CommandToken.ReadString(args, false)?.Split(',', ' ')) ?? Array.Empty(); + }); + + RegisterCommandHandler(false, str => byte.Parse(str)); + RegisterCommandHandler(false, str => str == "1" || str == "yes" || str == "y" || bool.Parse(str)); + RegisterCommandHandler(false, str => ushort.Parse(str)); + RegisterCommandHandler(false, str => uint.Parse(str)); + RegisterCommandHandler(false, IPAddress.Parse); + } + + /// + /// Register command handler + /// + /// Return type + /// Handler + private void RegisterCommandHandler(Func, bool, object> handler) + { + _handlers[typeof(TRet)] = handler; + } + + /// + /// Register command handler + /// + /// Base type + /// Return type + /// Can consume all + /// Handler + public void RegisterCommandHandler(bool canConsumeAll, Func handler) + { + _handlers[typeof(TRet)] = (args, _) => + { + var value = (T)_handlers[typeof(T)](args, canConsumeAll); + return handler(value); + }; + } + + /// + /// Register command handler + /// + /// Base type + /// Return type + /// Handler + public void RegisterCommandHandler(Func handler) + { + _handlers[typeof(TRet)] = (args, consumeAll) => + { + var value = (T)_handlers[typeof(T)](args, consumeAll); + return handler(value); + }; + } + + /// + /// Register commands + /// + /// Instance + /// Name + public void RegisterCommand(object instance, string? name = null) + { + if (!string.IsNullOrEmpty(name)) + { + _instances.Add(name.ToLowerInvariant(), instance); + } + + foreach (var method in instance.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) + { + foreach (var attribute in method.GetCustomAttributes()) + { + // Check handlers + + if (!method.GetParameters().All(u => u.ParameterType.IsEnum || _handlers.ContainsKey(u.ParameterType))) + { + throw new ArgumentException("Handler not found for the command: " + method); + } + + // Add command + + var command = new ConsoleCommandMethod(instance, method, attribute); + + if (!_verbs.TryGetValue(command.Key, out var commands)) + { + _verbs.Add(command.Key, new List(new[] { command })); + } + else + { + commands.Add(command); + } + } + } + } + + public void Run(string[] args) + { + if (Environment.UserInteractive) + { + if (args.Length == 1 && args[0] == "/install") + { + if (Environment.OSVersion.Platform != PlatformID.Win32NT) + { + ConsoleHelper.Warning("Only support for installing services on Windows."); + return; + } + string arguments = string.Format("create {0} start= auto binPath= \"{1}\"", ServiceName, Process.GetCurrentProcess().MainModule!.FileName); + if (!string.IsNullOrEmpty(Depends)) + { + arguments += string.Format(" depend= {0}", Depends); + } + Process? process = Process.Start(new ProcessStartInfo + { + Arguments = arguments, + FileName = Path.Combine(Environment.SystemDirectory, "sc.exe"), + RedirectStandardOutput = true, + UseShellExecute = false + }); + if (process is null) + { + ConsoleHelper.Error("Error installing the service with sc.exe."); + } + else + { + process.WaitForExit(); + Console.Write(process.StandardOutput.ReadToEnd()); + } + } + else if (args.Length == 1 && args[0] == "/uninstall") + { + if (Environment.OSVersion.Platform != PlatformID.Win32NT) + { + ConsoleHelper.Warning("Only support for installing services on Windows."); + return; + } + Process? process = Process.Start(new ProcessStartInfo + { + Arguments = string.Format("delete {0}", ServiceName), + FileName = Path.Combine(Environment.SystemDirectory, "sc.exe"), + RedirectStandardOutput = true, + UseShellExecute = false + }); + if (process is null) + { + ConsoleHelper.Error("Error installing the service with sc.exe."); + } + else + { + process.WaitForExit(); + Console.Write(process.StandardOutput.ReadToEnd()); + } + } + else + { + if (OnStart(args)) RunConsole(); + OnStop(); + } + } + else + { + Debug.Assert(Environment.OSVersion.Platform == PlatformID.Win32NT); +#pragma warning disable CA1416 + ServiceBase.Run(new ServiceProxy(this)); +#pragma warning restore CA1416 + } + } + + protected string? ReadLine() + { + Task readLineTask = Task.Run(Console.ReadLine); + + try + { + readLineTask.Wait(_shutdownTokenSource.Token); + } + catch (OperationCanceledException) + { + return null; + } + + return readLineTask.Result; + } + + public virtual void RunConsole() + { + _running = true; + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + try + { + Console.Title = ServiceName; + } + catch { } + + Console.ForegroundColor = ConsoleColor.DarkGreen; + Console.SetIn(new StreamReader(Console.OpenStandardInput(), Console.InputEncoding, false, ushort.MaxValue)); + + while (_running) + { + if (ShowPrompt) + { + Console.ForegroundColor = ConsoleColor.Green; + Console.Write($"{Prompt}> "); + } + + Console.ForegroundColor = ConsoleColor.Yellow; + string? line = ReadLine()?.Trim(); + if (line == null) break; + Console.ForegroundColor = ConsoleColor.White; + + try + { + if (!OnCommand(line)) + { + ConsoleHelper.Error("Command not found"); + } + } + catch (TargetInvocationException ex) when (ex.InnerException is not null) + { + ConsoleHelper.Error(ex.InnerException.Message); + } + catch (Exception ex) + { + ConsoleHelper.Error(ex.Message); + } + } + + Console.ResetColor(); + } + } +} diff --git a/src/Neo.ConsoleService/Neo.ConsoleService.csproj b/src/Neo.ConsoleService/Neo.ConsoleService.csproj new file mode 100644 index 0000000000..b4254a4cad --- /dev/null +++ b/src/Neo.ConsoleService/Neo.ConsoleService.csproj @@ -0,0 +1,15 @@ + + + + netstandard2.1;net8.0 + Neo.ConsoleService + enable + ../../bin/$(PackageId) + + + + + + + + diff --git a/src/Neo.ConsoleService/Properties/AssemblyInfo.cs b/src/Neo.ConsoleService/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..efa7107713 --- /dev/null +++ b/src/Neo.ConsoleService/Properties/AssemblyInfo.cs @@ -0,0 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// AssemblyInfo.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Neo.ConsoleService.Tests")] diff --git a/src/Neo.ConsoleService/ServiceProxy.cs b/src/Neo.ConsoleService/ServiceProxy.cs new file mode 100644 index 0000000000..25e7c8ae46 --- /dev/null +++ b/src/Neo.ConsoleService/ServiceProxy.cs @@ -0,0 +1,35 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ServiceProxy.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.ServiceProcess; + +namespace Neo.ConsoleService +{ + internal class ServiceProxy : ServiceBase + { + private readonly ConsoleServiceBase _service; + + public ServiceProxy(ConsoleServiceBase service) + { + _service = service; + } + + protected override void OnStart(string[] args) + { + _service.OnStart(args); + } + + protected override void OnStop() + { + _service.OnStop(); + } + } +} diff --git a/src/Neo.Cryptography.BLS12_381/Bls12.Adder.cs b/src/Neo.Cryptography.BLS12_381/Bls12.Adder.cs new file mode 100644 index 0000000000..3981f19a1b --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/Bls12.Adder.cs @@ -0,0 +1,71 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Bls12.Adder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Runtime.CompilerServices; +using static Neo.Cryptography.BLS12_381.MillerLoopUtility; + +namespace Neo.Cryptography.BLS12_381; + +partial class Bls12 +{ + class Adder : IMillerLoopDriver + { + public G2Projective Curve; + public readonly G2Affine Base; + public readonly G1Affine P; + + public Adder(in G1Affine p, in G2Affine q) + { + Curve = new(q); + Base = q; + P = p; + } + + Fp12 IMillerLoopDriver.DoublingStep(in Fp12 f) + { + var coeffs = DoublingStep(ref Curve); + return Ell(in f, in coeffs, in P); + } + + Fp12 IMillerLoopDriver.AdditionStep(in Fp12 f) + { + var coeffs = AdditionStep(ref Curve, in Base); + return Ell(in f, in coeffs, in P); + } + + #region IMillerLoopDriver + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Fp12 Square(in Fp12 f) => f.Square(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Fp12 Conjugate(in Fp12 f) => f.Conjugate(); + + public static Fp12 One + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Fp12.One; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + Fp12 IMillerLoopDriver.Square(in Fp12 f) => Adder.Square(f); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + Fp12 IMillerLoopDriver.Conjugate(in Fp12 f) => Adder.Conjugate(f); + Fp12 IMillerLoopDriver.One + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Adder.One; + } + + #endregion + } +} diff --git a/src/Neo.Cryptography.BLS12_381/Bls12.cs b/src/Neo.Cryptography.BLS12_381/Bls12.cs new file mode 100644 index 0000000000..10022ded4d --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/Bls12.cs @@ -0,0 +1,31 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Bls12.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using static Neo.Cryptography.BLS12_381.ConstantTimeUtility; +using static Neo.Cryptography.BLS12_381.MillerLoopUtility; + +namespace Neo.Cryptography.BLS12_381; + +public static partial class Bls12 +{ + public static Gt Pairing(in G1Affine p, in G2Affine q) + { + var either_identity = p.IsIdentity | q.IsIdentity; + var p2 = ConditionalSelect(in p, in G1Affine.Generator, either_identity); + var q2 = ConditionalSelect(in q, in G2Affine.Generator, either_identity); + + var adder = new Adder(p2, q2); + + var tmp = MillerLoop(adder); + var tmp2 = new MillerLoopResult(ConditionalSelect(in tmp, in Fp12.One, either_identity)); + return tmp2.FinalExponentiation(); + } +} diff --git a/src/Neo.Cryptography.BLS12_381/ConstantTimeUtility.cs b/src/Neo.Cryptography.BLS12_381/ConstantTimeUtility.cs new file mode 100644 index 0000000000..70517edeb1 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/ConstantTimeUtility.cs @@ -0,0 +1,42 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ConstantTimeUtility.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Neo.Cryptography.BLS12_381; + +public static class ConstantTimeUtility +{ + public static bool ConstantTimeEq(in T a, in T b) where T : unmanaged + { + ReadOnlySpan a_bytes = MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in a), 1)); + ReadOnlySpan b_bytes = MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in b), 1)); + ReadOnlySpan a_u64 = MemoryMarshal.Cast(a_bytes); + ReadOnlySpan b_u64 = MemoryMarshal.Cast(b_bytes); + ulong f = 0; + for (int i = 0; i < a_u64.Length; i++) + f |= a_u64[i] ^ b_u64[i]; + for (int i = a_u64.Length * sizeof(ulong); i < a_bytes.Length; i++) + f |= (ulong)a_bytes[i] ^ a_bytes[i]; + return f == 0; + } + + public static T ConditionalSelect(in T a, in T b, bool choice) where T : unmanaged + { + return choice ? b : a; + } + + public static void ConditionalAssign(this ref T self, in T other, bool choice) where T : unmanaged + { + self = ConditionalSelect(in self, in other, choice); + } +} diff --git a/src/Neo.Cryptography.BLS12_381/Constants.cs b/src/Neo.Cryptography.BLS12_381/Constants.cs new file mode 100644 index 0000000000..457b4d720b --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/Constants.cs @@ -0,0 +1,18 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Constants.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.Cryptography.BLS12_381; + +static class Constants +{ + public const ulong BLS_X = 0xd201_0000_0001_0000; + public const bool BLS_X_IS_NEGATIVE = true; +} diff --git a/src/Neo.Cryptography.BLS12_381/Fp.cs b/src/Neo.Cryptography.BLS12_381/Fp.cs new file mode 100644 index 0000000000..4b30b4fa5c --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/Fp.cs @@ -0,0 +1,491 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Fp.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Buffers.Binary; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using static Neo.Cryptography.BLS12_381.ConstantTimeUtility; +using static Neo.Cryptography.BLS12_381.FpConstants; +using static Neo.Cryptography.BLS12_381.MathUtility; + +namespace Neo.Cryptography.BLS12_381; + +[StructLayout(LayoutKind.Explicit, Size = Size)] +public readonly struct Fp : IEquatable, INumber +{ + public const int Size = 48; + public const int SizeL = Size / sizeof(ulong); + + private static readonly Fp _zero = new(); + + public static ref readonly Fp Zero => ref _zero; + public static ref readonly Fp One => ref R; + + public bool IsZero => this == Zero; + + public static Fp FromBytes(ReadOnlySpan data) + { + if (data.Length != Size) + throw new FormatException($"The argument `{nameof(data)}` must contain {Size} bytes."); + + Span tmp = stackalloc ulong[SizeL]; + BinaryPrimitives.TryReadUInt64BigEndian(data[0..8], out tmp[5]); + BinaryPrimitives.TryReadUInt64BigEndian(data[8..16], out tmp[4]); + BinaryPrimitives.TryReadUInt64BigEndian(data[16..24], out tmp[3]); + BinaryPrimitives.TryReadUInt64BigEndian(data[24..32], out tmp[2]); + BinaryPrimitives.TryReadUInt64BigEndian(data[32..40], out tmp[1]); + BinaryPrimitives.TryReadUInt64BigEndian(data[40..48], out tmp[0]); + ReadOnlySpan span = MemoryMarshal.Cast(tmp); + + try + { + return span[0] * R2; + } + finally + { + ulong borrow; + (_, borrow) = Sbb(tmp[0], MODULUS[0], 0); + (_, borrow) = Sbb(tmp[1], MODULUS[1], borrow); + (_, borrow) = Sbb(tmp[2], MODULUS[2], borrow); + (_, borrow) = Sbb(tmp[3], MODULUS[3], borrow); + (_, borrow) = Sbb(tmp[4], MODULUS[4], borrow); + (_, borrow) = Sbb(tmp[5], MODULUS[5], borrow); + if (borrow == 0) + { + // If the element is smaller than MODULUS then the subtraction will underflow. + // Otherwise, throws. + // Why not throw before return? + // Because we want to run the method in a constant time. + throw new FormatException(); + } + } + } + + internal static Fp FromRawUnchecked(ulong[] values) + { + if (values.Length != SizeL) + throw new FormatException($"The argument `{nameof(values)}` must contain {SizeL} entries."); + + return MemoryMarshal.Cast(values)[0]; + } + + public static Fp Random(RandomNumberGenerator rng) + { + Span buffer = stackalloc byte[Size * 2]; + rng.GetBytes(buffer); + Span d = MemoryMarshal.Cast(buffer); + return d[0] * R2 + d[1] * R3; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ReadOnlySpan GetSpan() + { + return MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in this), 1)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Span GetSpanU64() + { + return MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref Unsafe.AsRef(in this), 1)); + } + + public static bool operator ==(in Fp left, in Fp right) + { + return ConstantTimeEq(in left, in right); + } + + public static bool operator !=(in Fp left, in Fp right) + { + return !(left == right); + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj is not Fp other) return false; + return this == other; + } + + public bool Equals(Fp other) + { + return this == other; + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + + public byte[] ToArray() + { + byte[] result = new byte[Size]; + TryWrite(result); + return result; + } + + public bool TryWrite(Span buffer) + { + if (buffer.Length < Size) return false; + + ReadOnlySpan u64 = GetSpanU64(); + Fp tmp = MontgomeryReduce(u64[0], u64[1], u64[2], u64[3], u64[4], u64[5], 0, 0, 0, 0, 0, 0); + u64 = tmp.GetSpanU64(); + + BinaryPrimitives.WriteUInt64BigEndian(buffer[0..8], u64[5]); + BinaryPrimitives.WriteUInt64BigEndian(buffer[8..16], u64[4]); + BinaryPrimitives.WriteUInt64BigEndian(buffer[16..24], u64[3]); + BinaryPrimitives.WriteUInt64BigEndian(buffer[24..32], u64[2]); + BinaryPrimitives.WriteUInt64BigEndian(buffer[32..40], u64[1]); + BinaryPrimitives.WriteUInt64BigEndian(buffer[40..48], u64[0]); + + return true; + } + + public override string ToString() + { + var output = string.Empty; + foreach (var b in ToArray()) + output += b.ToString("x2"); + + return "0x" + output; + } + + public bool LexicographicallyLargest() + { + ReadOnlySpan s = GetSpanU64(); + Fp tmp = MontgomeryReduce(s[0], s[1], s[2], s[3], s[4], s[5], 0, 0, 0, 0, 0, 0); + ReadOnlySpan t = tmp.GetSpanU64(); + ulong borrow; + + (_, borrow) = Sbb(t[0], 0xdcff_7fff_ffff_d556, 0); + (_, borrow) = Sbb(t[1], 0x0f55_ffff_58a9_ffff, borrow); + (_, borrow) = Sbb(t[2], 0xb398_6950_7b58_7b12, borrow); + (_, borrow) = Sbb(t[3], 0xb23b_a5c2_79c2_895f, borrow); + (_, borrow) = Sbb(t[4], 0x258d_d3db_21a5_d66b, borrow); + (_, borrow) = Sbb(t[5], 0x0d00_88f5_1cbf_f34d, borrow); + + return borrow == 0; + } + + public Fp Sqrt() + { + // We use Shank's method, as p = 3 (mod 4). This means + // we only need to exponentiate by (p + 1) / 4. This only + // works for elements that are actually quadratic residue, + // so we check that we got the correct result at the end. + Fp result = this.PowVartime(P_1_4); + if (result.Square() != this) throw new ArithmeticException(); + return result; + } + + public Fp Invert() + { + if (!TryInvert(out Fp result)) + throw new DivideByZeroException(); + return result; + } + + public bool TryInvert(out Fp result) + { + // Exponentiate by p - 2 + result = this.PowVartime(P_2); + + // Why not return before Pow() if IsZero? + // Because we want to run the method in a constant time. + return !IsZero; + } + + private Fp SubtractP() + { + Fp result; + ReadOnlySpan s = GetSpanU64(); + Span r = result.GetSpanU64(); + ulong borrow; + + (r[0], borrow) = Sbb(s[0], MODULUS[0], 0); + (r[1], borrow) = Sbb(s[1], MODULUS[1], borrow); + (r[2], borrow) = Sbb(s[2], MODULUS[2], borrow); + (r[3], borrow) = Sbb(s[3], MODULUS[3], borrow); + (r[4], borrow) = Sbb(s[4], MODULUS[4], borrow); + (r[5], borrow) = Sbb(s[5], MODULUS[5], borrow); + + borrow = borrow == 0 ? ulong.MinValue : ulong.MaxValue; + r[0] = (s[0] & borrow) | (r[0] & ~borrow); + r[1] = (s[1] & borrow) | (r[1] & ~borrow); + r[2] = (s[2] & borrow) | (r[2] & ~borrow); + r[3] = (s[3] & borrow) | (r[3] & ~borrow); + r[4] = (s[4] & borrow) | (r[4] & ~borrow); + r[5] = (s[5] & borrow) | (r[5] & ~borrow); + + return result; + } + + public static Fp operator +(in Fp a, in Fp b) + { + Fp result; + ReadOnlySpan s = a.GetSpanU64(), r = b.GetSpanU64(); + Span d = result.GetSpanU64(); + + ulong carry = 0; + (d[0], carry) = Adc(s[0], r[0], carry); + (d[1], carry) = Adc(s[1], r[1], carry); + (d[2], carry) = Adc(s[2], r[2], carry); + (d[3], carry) = Adc(s[3], r[3], carry); + (d[4], carry) = Adc(s[4], r[4], carry); + (d[5], _) = Adc(s[5], r[5], carry); + + return result.SubtractP(); + } + + public static Fp operator -(in Fp a) + { + Fp result; + ReadOnlySpan self = a.GetSpanU64(); + Span d = result.GetSpanU64(); + + ulong borrow = 0; + (d[0], borrow) = Sbb(MODULUS[0], self[0], borrow); + (d[1], borrow) = Sbb(MODULUS[1], self[1], borrow); + (d[2], borrow) = Sbb(MODULUS[2], self[2], borrow); + (d[3], borrow) = Sbb(MODULUS[3], self[3], borrow); + (d[4], borrow) = Sbb(MODULUS[4], self[4], borrow); + (d[5], _) = Sbb(MODULUS[5], self[5], borrow); + + ulong mask = a.IsZero ? ulong.MinValue : ulong.MaxValue; + d[0] &= mask; + d[1] &= mask; + d[2] &= mask; + d[3] &= mask; + d[4] &= mask; + d[5] &= mask; + + return result; + } + + public static Fp operator -(in Fp a, in Fp b) + { + return -b + a; + } + + public static Fp SumOfProducts(ReadOnlySpan a, ReadOnlySpan b) + { + int length = a.Length; + if (length != b.Length) + throw new ArgumentException("The lengths of the two arrays must be the same."); + + Fp result; + ReadOnlySpan au = MemoryMarshal.Cast(a); + ReadOnlySpan bu = MemoryMarshal.Cast(b); + Span u = result.GetSpanU64(); + + for (int j = 0; j < 6; j++) + { + ulong carry; + + var (t0, t1, t2, t3, t4, t5, t6) = (u[0], u[1], u[2], u[3], u[4], u[5], 0ul); + for (int i = 0; i < length; i++) + { + (t0, carry) = Mac(t0, au[i * SizeL + j], bu[i * SizeL + 0], 0); + (t1, carry) = Mac(t1, au[i * SizeL + j], bu[i * SizeL + 1], carry); + (t2, carry) = Mac(t2, au[i * SizeL + j], bu[i * SizeL + 2], carry); + (t3, carry) = Mac(t3, au[i * SizeL + j], bu[i * SizeL + 3], carry); + (t4, carry) = Mac(t4, au[i * SizeL + j], bu[i * SizeL + 4], carry); + (t5, carry) = Mac(t5, au[i * SizeL + j], bu[i * SizeL + 5], carry); + (t6, _) = Adc(t6, 0, carry); + } + + ulong k = unchecked(t0 * INV); + (_, carry) = Mac(t0, k, MODULUS[0], 0); + (u[0], carry) = Mac(t1, k, MODULUS[1], carry); + (u[1], carry) = Mac(t2, k, MODULUS[2], carry); + (u[2], carry) = Mac(t3, k, MODULUS[3], carry); + (u[3], carry) = Mac(t4, k, MODULUS[4], carry); + (u[4], carry) = Mac(t5, k, MODULUS[5], carry); + (u[5], _) = Adc(t6, 0, carry); + } + + return result.SubtractP(); + } + + private static Fp MontgomeryReduce(ulong r0, ulong r1, ulong r2, ulong r3, ulong r4, ulong r5, ulong r6, ulong r7, ulong r8, ulong r9, ulong r10, ulong r11) + { + ulong carry, carry2; + + ulong k = unchecked(r0 * INV); + (_, carry) = Mac(r0, k, MODULUS[0], 0); + (r1, carry) = Mac(r1, k, MODULUS[1], carry); + (r2, carry) = Mac(r2, k, MODULUS[2], carry); + (r3, carry) = Mac(r3, k, MODULUS[3], carry); + (r4, carry) = Mac(r4, k, MODULUS[4], carry); + (r5, carry) = Mac(r5, k, MODULUS[5], carry); + (r6, carry2) = Adc(r6, 0, carry); + + k = unchecked(r1 * INV); + (_, carry) = Mac(r1, k, MODULUS[0], 0); + (r2, carry) = Mac(r2, k, MODULUS[1], carry); + (r3, carry) = Mac(r3, k, MODULUS[2], carry); + (r4, carry) = Mac(r4, k, MODULUS[3], carry); + (r5, carry) = Mac(r5, k, MODULUS[4], carry); + (r6, carry) = Mac(r6, k, MODULUS[5], carry); + (r7, carry2) = Adc(r7, carry2, carry); + + k = unchecked(r2 * INV); + (_, carry) = Mac(r2, k, MODULUS[0], 0); + (r3, carry) = Mac(r3, k, MODULUS[1], carry); + (r4, carry) = Mac(r4, k, MODULUS[2], carry); + (r5, carry) = Mac(r5, k, MODULUS[3], carry); + (r6, carry) = Mac(r6, k, MODULUS[4], carry); + (r7, carry) = Mac(r7, k, MODULUS[5], carry); + (r8, carry2) = Adc(r8, carry2, carry); + + k = unchecked(r3 * INV); + (_, carry) = Mac(r3, k, MODULUS[0], 0); + (r4, carry) = Mac(r4, k, MODULUS[1], carry); + (r5, carry) = Mac(r5, k, MODULUS[2], carry); + (r6, carry) = Mac(r6, k, MODULUS[3], carry); + (r7, carry) = Mac(r7, k, MODULUS[4], carry); + (r8, carry) = Mac(r8, k, MODULUS[5], carry); + (r9, carry2) = Adc(r9, carry2, carry); + + k = unchecked(r4 * INV); + (_, carry) = Mac(r4, k, MODULUS[0], 0); + (r5, carry) = Mac(r5, k, MODULUS[1], carry); + (r6, carry) = Mac(r6, k, MODULUS[2], carry); + (r7, carry) = Mac(r7, k, MODULUS[3], carry); + (r8, carry) = Mac(r8, k, MODULUS[4], carry); + (r9, carry) = Mac(r9, k, MODULUS[5], carry); + (r10, carry2) = Adc(r10, carry2, carry); + + k = unchecked(r5 * INV); + (_, carry) = Mac(r5, k, MODULUS[0], 0); + (r6, carry) = Mac(r6, k, MODULUS[1], carry); + (r7, carry) = Mac(r7, k, MODULUS[2], carry); + (r8, carry) = Mac(r8, k, MODULUS[3], carry); + (r9, carry) = Mac(r9, k, MODULUS[4], carry); + (r10, carry) = Mac(r10, k, MODULUS[5], carry); + (r11, _) = Adc(r11, carry2, carry); + + ReadOnlySpan tmp = stackalloc[] { r6, r7, r8, r9, r10, r11 }; + return MemoryMarshal.Cast(tmp)[0].SubtractP(); + } + + public static Fp operator *(in Fp a, in Fp b) + { + ReadOnlySpan s = a.GetSpanU64(), r = b.GetSpanU64(); + Span t = stackalloc ulong[SizeL * 2]; + ulong carry; + + (t[0], carry) = Mac(0, s[0], r[0], 0); + (t[1], carry) = Mac(0, s[0], r[1], carry); + (t[2], carry) = Mac(0, s[0], r[2], carry); + (t[3], carry) = Mac(0, s[0], r[3], carry); + (t[4], carry) = Mac(0, s[0], r[4], carry); + (t[5], t[6]) = Mac(0, s[0], r[5], carry); + + (t[1], carry) = Mac(t[1], s[1], r[0], 0); + (t[2], carry) = Mac(t[2], s[1], r[1], carry); + (t[3], carry) = Mac(t[3], s[1], r[2], carry); + (t[4], carry) = Mac(t[4], s[1], r[3], carry); + (t[5], carry) = Mac(t[5], s[1], r[4], carry); + (t[6], t[7]) = Mac(t[6], s[1], r[5], carry); + + (t[2], carry) = Mac(t[2], s[2], r[0], 0); + (t[3], carry) = Mac(t[3], s[2], r[1], carry); + (t[4], carry) = Mac(t[4], s[2], r[2], carry); + (t[5], carry) = Mac(t[5], s[2], r[3], carry); + (t[6], carry) = Mac(t[6], s[2], r[4], carry); + (t[7], t[8]) = Mac(t[7], s[2], r[5], carry); + (t[3], carry) = Mac(t[3], s[3], r[0], 0); + (t[4], carry) = Mac(t[4], s[3], r[1], carry); + (t[5], carry) = Mac(t[5], s[3], r[2], carry); + (t[6], carry) = Mac(t[6], s[3], r[3], carry); + (t[7], carry) = Mac(t[7], s[3], r[4], carry); + (t[8], t[9]) = Mac(t[8], s[3], r[5], carry); + (t[4], carry) = Mac(t[4], s[4], r[0], 0); + (t[5], carry) = Mac(t[5], s[4], r[1], carry); + (t[6], carry) = Mac(t[6], s[4], r[2], carry); + (t[7], carry) = Mac(t[7], s[4], r[3], carry); + (t[8], carry) = Mac(t[8], s[4], r[4], carry); + (t[9], t[10]) = Mac(t[9], s[4], r[5], carry); + (t[5], carry) = Mac(t[5], s[5], r[0], 0); + (t[6], carry) = Mac(t[6], s[5], r[1], carry); + (t[7], carry) = Mac(t[7], s[5], r[2], carry); + (t[8], carry) = Mac(t[8], s[5], r[3], carry); + (t[9], carry) = Mac(t[9], s[5], r[4], carry); + (t[10], t[11]) = Mac(t[10], s[5], r[5], carry); + + return MontgomeryReduce(t[0], t[1], t[2], t[3], t[4], t[5], t[6], t[7], t[8], t[9], t[10], t[11]); + } + + public Fp Square() + { + ReadOnlySpan self = GetSpanU64(); + ulong t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11; + ulong carry; + + (t1, carry) = Mac(0, self[0], self[1], 0); + (t2, carry) = Mac(0, self[0], self[2], carry); + (t3, carry) = Mac(0, self[0], self[3], carry); + (t4, carry) = Mac(0, self[0], self[4], carry); + (t5, t6) = Mac(0, self[0], self[5], carry); + + (t3, carry) = Mac(t3, self[1], self[2], 0); + (t4, carry) = Mac(t4, self[1], self[3], carry); + (t5, carry) = Mac(t5, self[1], self[4], carry); + (t6, t7) = Mac(t6, self[1], self[5], carry); + + (t5, carry) = Mac(t5, self[2], self[3], 0); + (t6, carry) = Mac(t6, self[2], self[4], carry); + (t7, t8) = Mac(t7, self[2], self[5], carry); + + (t7, carry) = Mac(t7, self[3], self[4], 0); + (t8, t9) = Mac(t8, self[3], self[5], carry); + + (t9, t10) = Mac(t9, self[4], self[5], 0); + + t11 = t10 >> 63; + t10 = (t10 << 1) | (t9 >> 63); + t9 = (t9 << 1) | (t8 >> 63); + t8 = (t8 << 1) | (t7 >> 63); + t7 = (t7 << 1) | (t6 >> 63); + t6 = (t6 << 1) | (t5 >> 63); + t5 = (t5 << 1) | (t4 >> 63); + t4 = (t4 << 1) | (t3 >> 63); + t3 = (t3 << 1) | (t2 >> 63); + t2 = (t2 << 1) | (t1 >> 63); + t1 <<= 1; + + (t0, carry) = Mac(0, self[0], self[0], 0); + (t1, carry) = Adc(t1, carry, 0); + (t2, carry) = Mac(t2, self[1], self[1], carry); + (t3, carry) = Adc(t3, carry, 0); + (t4, carry) = Mac(t4, self[2], self[2], carry); + (t5, carry) = Adc(t5, carry, 0); + (t6, carry) = Mac(t6, self[3], self[3], carry); + (t7, carry) = Adc(t7, carry, 0); + (t8, carry) = Mac(t8, self[4], self[4], carry); + (t9, carry) = Adc(t9, carry, 0); + (t10, carry) = Mac(t10, self[5], self[5], carry); + (t11, _) = Adc(t11, carry, 0); + + return MontgomeryReduce(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11); + } + + #region Instance math methods + + public Fp Negate() => -this; + public Fp Multiply(in Fp value) => this * value; + public Fp Sum(in Fp value) => this + value; + public Fp Subtract(in Fp value) => this - value; + + #endregion +} diff --git a/src/Neo.Cryptography.BLS12_381/Fp12.cs b/src/Neo.Cryptography.BLS12_381/Fp12.cs new file mode 100644 index 0000000000..2ed7ee988a --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/Fp12.cs @@ -0,0 +1,218 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Fp12.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Security.Cryptography; + +namespace Neo.Cryptography.BLS12_381; + +[StructLayout(LayoutKind.Explicit, Size = Size)] +public readonly struct Fp12 : IEquatable, INumber +{ + [FieldOffset(0)] + public readonly Fp6 C0; + [FieldOffset(Fp6.Size)] + public readonly Fp6 C1; + + public const int Size = Fp6.Size * 2; + + private static readonly Fp12 _zero = new(); + private static readonly Fp12 _one = new(in Fp6.One); + + public static ref readonly Fp12 Zero => ref _zero; + public static ref readonly Fp12 One => ref _one; + + public bool IsZero => C0.IsZero & C1.IsZero; + + public Fp12(in Fp f) + : this(new Fp6(in f), in Fp6.Zero) + { + } + + public Fp12(in Fp2 f) + : this(new Fp6(in f), in Fp6.Zero) + { + } + + public Fp12(in Fp6 f) + : this(in f, in Fp6.Zero) + { + } + + public Fp12(in Fp6 c0, in Fp6 c1) + { + C0 = c0; + C1 = c1; + } + + public static Fp12 FromBytes(ReadOnlySpan data) + { + if (data.Length != Size) + throw new FormatException($"The argument `{nameof(data)}` must contain {Size} bytes."); + Fp6 c0 = Fp6.FromBytes(data[Fp6.Size..]); + Fp6 c1 = Fp6.FromBytes(data[..Fp6.Size]); + return new(in c0, in c1); + } + + public static bool operator ==(in Fp12 a, in Fp12 b) + { + return a.C0 == b.C0 & a.C1 == b.C1; + } + + public static bool operator !=(in Fp12 a, in Fp12 b) + { + return !(a == b); + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj is not Fp12 other) return false; + return this == other; + } + + public bool Equals(Fp12 other) + { + return this == other; + } + + public override int GetHashCode() + { + return C0.GetHashCode() ^ C1.GetHashCode(); + } + + public byte[] ToArray() + { + byte[] result = new byte[Size]; + TryWrite(result); + return result; + } + + public bool TryWrite(Span buffer) + { + if (buffer.Length < Size) return false; + C0.TryWrite(buffer[Fp6.Size..Size]); + C1.TryWrite(buffer[0..Fp6.Size]); + return true; + } + + public static Fp12 Random(RandomNumberGenerator rng) + { + return new(Fp6.Random(rng), Fp6.Random(rng)); + } + + internal Fp12 MulBy_014(in Fp2 c0, in Fp2 c1, in Fp2 c4) + { + var aa = C0.MulBy_01(in c0, in c1); + var bb = C1.MulBy_1(in c4); + var o = c1 + c4; + var _c1 = C1 + C0; + _c1 = _c1.MulBy_01(in c0, in o); + _c1 = _c1 - aa - bb; + var _c0 = bb; + _c0 = _c0.MulByNonresidue(); + _c0 += aa; + + return new Fp12(in _c0, in _c1); + } + + public Fp12 Conjugate() + { + return new Fp12(in C0, -C1); + } + + public Fp12 FrobeniusMap() + { + var c0 = C0.FrobeniusMap(); + var c1 = C1.FrobeniusMap(); + + // c1 = c1 * (u + 1)^((p - 1) / 6) + c1 *= new Fp6(new Fp2( + Fp.FromRawUnchecked(new ulong[] + { + 0x0708_9552_b319_d465, + 0xc669_5f92_b50a_8313, + 0x97e8_3ccc_d117_228f, + 0xa35b_aeca_b2dc_29ee, + 0x1ce3_93ea_5daa_ce4d, + 0x08f2_220f_b0fb_66eb + }), Fp.FromRawUnchecked(new ulong[] + { + 0xb2f6_6aad_4ce5_d646, + 0x5842_a06b_fc49_7cec, + 0xcf48_95d4_2599_d394, + 0xc11b_9cba_40a8_e8d0, + 0x2e38_13cb_e5a0_de89, + 0x110e_efda_8884_7faf + }))); + + return new Fp12(in c0, in c1); + } + + public Fp12 Square() + { + var ab = C0 * C1; + var c0c1 = C0 + C1; + var c0 = C1.MulByNonresidue(); + c0 += C0; + c0 *= c0c1; + c0 -= ab; + var c1 = ab + ab; + c0 -= ab.MulByNonresidue(); + + return new Fp12(in c0, in c1); + } + + public Fp12 Invert() + { + Fp6 t = (C0.Square() - C1.Square().MulByNonresidue()).Invert(); + return new Fp12(C0 * t, C1 * -t); + } + + public static Fp12 operator -(in Fp12 a) + { + return new Fp12(-a.C0, -a.C1); + } + + public static Fp12 operator +(in Fp12 a, in Fp12 b) + { + return new Fp12(a.C0 + b.C0, a.C1 + b.C1); + } + + public static Fp12 operator -(in Fp12 a, in Fp12 b) + { + return new Fp12(a.C0 - b.C0, a.C1 - b.C1); + } + + public static Fp12 operator *(in Fp12 a, in Fp12 b) + { + var aa = a.C0 * b.C0; + var bb = a.C1 * b.C1; + var o = b.C0 + b.C1; + var c1 = a.C1 + a.C0; + c1 *= o; + c1 -= aa; + c1 -= bb; + var c0 = bb.MulByNonresidue(); + c0 += aa; + + return new Fp12(in c0, in c1); + } + + #region Instance math methods + + public Fp12 Negate() => -this; + public Fp12 Multiply(in Fp12 value) => this * value; + public Fp12 Sum(in Fp12 value) => this + value; + public Fp12 Subtract(in Fp12 value) => this - value; + + #endregion +} diff --git a/src/Neo.Cryptography.BLS12_381/Fp2.cs b/src/Neo.Cryptography.BLS12_381/Fp2.cs new file mode 100644 index 0000000000..4ca3830970 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/Fp2.cs @@ -0,0 +1,274 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Fp2.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using static Neo.Cryptography.BLS12_381.ConstantTimeUtility; + +namespace Neo.Cryptography.BLS12_381; + +[StructLayout(LayoutKind.Explicit, Size = Size)] +public readonly struct Fp2 : IEquatable, INumber +{ + [FieldOffset(0)] + public readonly Fp C0; + [FieldOffset(Fp.Size)] + public readonly Fp C1; + + public const int Size = Fp.Size * 2; + + private static readonly Fp2 _zero = new(); + private static readonly Fp2 _one = new(in Fp.One); + + public static ref readonly Fp2 Zero => ref _zero; + public static ref readonly Fp2 One => ref _one; + + public bool IsZero => C0.IsZero & C1.IsZero; + + public Fp2(in Fp f) + : this(in f, in Fp.Zero) + { + } + + public Fp2(in Fp c0, in Fp c1) + { + C0 = c0; + C1 = c1; + } + + public static Fp2 FromBytes(ReadOnlySpan data) + { + if (data.Length != Size) + throw new FormatException($"The argument `{nameof(data)}` must contain {Size} bytes."); + Fp c0 = Fp.FromBytes(data[Fp.Size..]); + Fp c1 = Fp.FromBytes(data[..Fp.Size]); + return new(in c0, in c1); + } + + public static Fp2 Random(RandomNumberGenerator rng) + { + return new(Fp.Random(rng), Fp.Random(rng)); + } + + public static bool operator ==(in Fp2 a, in Fp2 b) + { + return a.C0 == b.C0 & a.C1 == b.C1; + } + + public static bool operator !=(in Fp2 a, in Fp2 b) + { + return !(a == b); + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj is not Fp2 other) return false; + return this == other; + } + + public bool Equals(Fp2 other) + { + return this == other; + } + + public override int GetHashCode() + { + return C0.GetHashCode() ^ C1.GetHashCode(); + } + + public byte[] ToArray() + { + byte[] result = new byte[Size]; + TryWrite(result); + return result; + } + + public bool TryWrite(Span buffer) + { + if (buffer.Length < Size) return false; + C0.TryWrite(buffer[Fp.Size..Size]); + C1.TryWrite(buffer[0..Fp.Size]); + return true; + } + + public Fp2 FrobeniusMap() + { + // This is always just a conjugation. If you're curious why, here's + // an article about it: https://alicebob.cryptoland.net/the-frobenius-endomorphism-with-finite-fields/ + return Conjugate(); + } + + public Fp2 Conjugate() + { + return new(in C0, -C1); + } + + public Fp2 MulByNonresidue() + { + // Multiply a + bu by u + 1, getting + // au + a + bu^2 + bu + // and because u^2 = -1, we get + // (a - b) + (a + b)u + + return new(C0 - C1, C0 + C1); + } + + public bool LexicographicallyLargest() + { + // If this element's c1 coefficient is lexicographically largest + // then it is lexicographically largest. Otherwise, in the event + // the c1 coefficient is zero and the c0 coefficient is + // lexicographically largest, then this element is lexicographically + // largest. + + return C1.LexicographicallyLargest() | (C1.IsZero & C0.LexicographicallyLargest()); + } + + public Fp2 Square() + { + // Complex squaring: + // + // v0 = c0 * c1 + // c0' = (c0 + c1) * (c0 + \beta*c1) - v0 - \beta * v0 + // c1' = 2 * v0 + // + // In BLS12-381's F_{p^2}, our \beta is -1 so we + // can modify this formula: + // + // c0' = (c0 + c1) * (c0 - c1) + // c1' = 2 * c0 * c1 + + var a = C0 + C1; + var b = C0 - C1; + var c = C0 + C0; + + return new(a * b, c * C1); + } + + public static Fp2 operator *(in Fp2 a, in Fp2 b) + { + // F_{p^2} x F_{p^2} multiplication implemented with operand scanning (schoolbook) + // computes the result as: + // + // a·b = (a_0 b_0 + a_1 b_1 β) + (a_0 b_1 + a_1 b_0)i + // + // In BLS12-381's F_{p^2}, our β is -1, so the resulting F_{p^2} element is: + // + // c_0 = a_0 b_0 - a_1 b_1 + // c_1 = a_0 b_1 + a_1 b_0 + // + // Each of these is a "sum of products", which we can compute efficiently. + + return new( + Fp.SumOfProducts(stackalloc[] { a.C0, -a.C1 }, stackalloc[] { b.C0, b.C1 }), + Fp.SumOfProducts(stackalloc[] { a.C0, a.C1 }, stackalloc[] { b.C1, b.C0 }) + ); + } + + public static Fp2 operator +(in Fp2 a, in Fp2 b) + { + return new(a.C0 + b.C0, a.C1 + b.C1); + } + + public static Fp2 operator -(in Fp2 a, in Fp2 b) + { + return new(a.C0 - b.C0, a.C1 - b.C1); + } + + public static Fp2 operator -(in Fp2 a) + { + return new(-a.C0, -a.C1); + } + + public Fp2 Sqrt() + { + // Algorithm 9, https://eprint.iacr.org/2012/685.pdf + // with constant time modifications. + + // a1 = self^((p - 3) / 4) + var a1 = this.PowVartime(new ulong[] + { + 0xee7f_bfff_ffff_eaaa, + 0x07aa_ffff_ac54_ffff, + 0xd9cc_34a8_3dac_3d89, + 0xd91d_d2e1_3ce1_44af, + 0x92c6_e9ed_90d2_eb35, + 0x0680_447a_8e5f_f9a6 + }); + + // alpha = a1^2 * self = self^((p - 3) / 2 + 1) = self^((p - 1) / 2) + var alpha = a1.Square() * this; + + // x0 = self^((p + 1) / 4) + var x0 = a1 * this; + + // (1 + alpha)^((q - 1) // 2) * x0 + var sqrt = (alpha + One).PowVartime(new ulong[] { + 0xdcff_7fff_ffff_d555, + 0x0f55_ffff_58a9_ffff, + 0xb398_6950_7b58_7b12, + 0xb23b_a5c2_79c2_895f, + 0x258d_d3db_21a5_d66b, + 0x0d00_88f5_1cbf_f34d, + }) * x0; + + // In the event that alpha = -1, the element is order p - 1 and so + // we're just trying to get the square of an element of the subfield + // Fp. This is given by x0 * u, since u = sqrt(-1). Since the element + // x0 = a + bu has b = 0, the solution is therefore au. + sqrt = ConditionalSelect(in sqrt, new(-x0.C1, in x0.C0), alpha == -One); + + sqrt = ConditionalSelect(in sqrt, in Zero, IsZero); + + // Only return the result if it's really the square root (and so + // self is actually quadratic nonresidue) + if (sqrt.Square() != this) throw new ArithmeticException(); + return sqrt; + } + + public Fp2 Invert() + { + if (!TryInvert(out Fp2 result)) + throw new DivideByZeroException(); + return result; + } + + public bool TryInvert(out Fp2 result) + { + // We wish to find the multiplicative inverse of a nonzero + // element a + bu in Fp2. We leverage an identity + // + // (a + bu)(a - bu) = a^2 + b^2 + // + // which holds because u^2 = -1. This can be rewritten as + // + // (a + bu)(a - bu)/(a^2 + b^2) = 1 + // + // because a^2 + b^2 = 0 has no nonzero solutions for (a, b). + // This gives that (a - bu)/(a^2 + b^2) is the inverse + // of (a + bu). Importantly, this can be computing using + // only a single inversion in Fp. + + bool s = (C0.Square() + C1.Square()).TryInvert(out Fp t); + result = new Fp2(C0 * t, C1 * -t); + return s; + } + + #region Instance math methods + + public Fp2 Negate() => -this; + public Fp2 Multiply(in Fp2 value) => this * value; + public Fp2 Sum(in Fp2 value) => this + value; + public Fp2 Subtract(in Fp2 value) => this - value; + + #endregion +} diff --git a/src/Neo.Cryptography.BLS12_381/Fp6.cs b/src/Neo.Cryptography.BLS12_381/Fp6.cs new file mode 100644 index 0000000000..540bfc8a36 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/Fp6.cs @@ -0,0 +1,308 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Fp6.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Security.Cryptography; + +namespace Neo.Cryptography.BLS12_381; + +[StructLayout(LayoutKind.Explicit, Size = Size)] +public readonly struct Fp6 : IEquatable, INumber +{ + [FieldOffset(0)] + public readonly Fp2 C0; + [FieldOffset(Fp2.Size)] + public readonly Fp2 C1; + [FieldOffset(Fp2.Size * 2)] + public readonly Fp2 C2; + + public const int Size = Fp2.Size * 3; + + private static readonly Fp6 _zero = new(); + private static readonly Fp6 _one = new(in Fp2.One); + + public static ref readonly Fp6 Zero => ref _zero; + public static ref readonly Fp6 One => ref _one; + + public bool IsZero => C0.IsZero & C1.IsZero & C2.IsZero; + + public Fp6(in Fp f) + : this(new Fp2(in f), in Fp2.Zero, in Fp2.Zero) + { + } + + public Fp6(in Fp2 f) + : this(in f, in Fp2.Zero, in Fp2.Zero) + { + } + + public Fp6(in Fp2 c0, in Fp2 c1, in Fp2 c2) + { + C0 = c0; + C1 = c1; + C2 = c2; + } + + public static Fp6 FromBytes(ReadOnlySpan data) + { + if (data.Length != Size) + throw new FormatException($"The argument `{nameof(data)}` must contain {Size} bytes."); + Fp2 c0 = Fp2.FromBytes(data[(Fp2.Size * 2)..]); + Fp2 c1 = Fp2.FromBytes(data[Fp2.Size..(Fp2.Size * 2)]); + Fp2 c2 = Fp2.FromBytes(data[..Fp2.Size]); + return new(in c0, in c1, in c2); + } + + public static bool operator ==(in Fp6 a, in Fp6 b) + { + return a.C0 == b.C0 & a.C1 == b.C1 & a.C2 == b.C2; + } + + public static bool operator !=(in Fp6 a, in Fp6 b) + { + return !(a == b); + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj is not Fp6 other) return false; + return this == other; + } + + public bool Equals(Fp6 other) + { + return this == other; + } + + public override int GetHashCode() + { + return C0.GetHashCode() ^ C1.GetHashCode() ^ C2.GetHashCode(); + } + + public byte[] ToArray() + { + byte[] result = new byte[Size]; + TryWrite(result); + return result; + } + + public bool TryWrite(Span buffer) + { + if (buffer.Length < Size) return false; + C0.TryWrite(buffer[(Fp2.Size * 2)..Size]); + C1.TryWrite(buffer[Fp2.Size..(Fp2.Size * 2)]); + C2.TryWrite(buffer[0..Fp2.Size]); + return true; + } + + public static Fp6 Random(RandomNumberGenerator rng) + { + return new(Fp2.Random(rng), Fp2.Random(rng), Fp2.Random(rng)); + } + + internal Fp6 MulBy_1(in Fp2 c1) + { + var b_b = C1 * c1; + + var t1 = (C1 + C2) * c1 - b_b; + t1 = t1.MulByNonresidue(); + + var t2 = (C0 + C1) * c1 - b_b; + + return new Fp6(in t1, in t2, in b_b); + } + + internal Fp6 MulBy_01(in Fp2 c0, in Fp2 c1) + { + var a_a = C0 * c0; + var b_b = C1 * c1; + + var t1 = (C1 + C2) * c1 - b_b; + t1 = t1.MulByNonresidue() + a_a; + + var t2 = (c0 + c1) * (C0 + C1) - a_a - b_b; + + var t3 = (C0 + C2) * c0 - a_a + b_b; + + return new Fp6(in t1, in t2, in t3); + } + + public Fp6 MulByNonresidue() + { + // Given a + bv + cv^2, this produces + // av + bv^2 + cv^3 + // but because v^3 = u + 1, we have + // c(u + 1) + av + v^2 + + return new Fp6(C2.MulByNonresidue(), in C0, in C1); + } + + public Fp6 FrobeniusMap() + { + var c0 = C0.FrobeniusMap(); + var c1 = C1.FrobeniusMap(); + var c2 = C2.FrobeniusMap(); + + // c1 = c1 * (u + 1)^((p - 1) / 3) + c1 *= new Fp2(in Fp.Zero, Fp.FromRawUnchecked(new ulong[] + { + 0xcd03_c9e4_8671_f071, + 0x5dab_2246_1fcd_a5d2, + 0x5870_42af_d385_1b95, + 0x8eb6_0ebe_01ba_cb9e, + 0x03f9_7d6e_83d0_50d2, + 0x18f0_2065_5463_8741 + })); + + // c2 = c2 * (u + 1)^((2p - 2) / 3) + c2 *= new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x890d_c9e4_8675_45c3, + 0x2af3_2253_3285_a5d5, + 0x5088_0866_309b_7e2c, + 0xa20d_1b8c_7e88_1024, + 0x14e4_f04f_e2db_9068, + 0x14e5_6d3f_1564_853a + }), in Fp.Zero); + + return new Fp6(c0, c1, c2); + } + + public Fp6 Square() + { + var s0 = C0.Square(); + var ab = C0 * C1; + var s1 = ab + ab; + var s2 = (C0 - C1 + C2).Square(); + var bc = C1 * C2; + var s3 = bc + bc; + var s4 = C2.Square(); + + return new Fp6( + s3.MulByNonresidue() + s0, + s4.MulByNonresidue() + s1, + s1 + s2 + s3 - s0 - s4 + ); + } + + public Fp6 Invert() + { + var c0 = (C1 * C2).MulByNonresidue(); + c0 = C0.Square() - c0; + + var c1 = C2.Square().MulByNonresidue(); + c1 -= C0 * C1; + + var c2 = C1.Square(); + c2 -= C0 * C2; + + var t = (C1 * c2 + C2 * c1).MulByNonresidue(); + t += C0 * c0; + + t = t.Invert(); + return new Fp6(t * c0, t * c1, t * c2); + } + + public static Fp6 operator -(in Fp6 a) + { + return new Fp6(-a.C0, -a.C1, -a.C2); + } + + public static Fp6 operator +(in Fp6 a, in Fp6 b) + { + return new Fp6(a.C0 + b.C0, a.C1 + b.C1, a.C2 + b.C2); + } + + public static Fp6 operator -(in Fp6 a, in Fp6 b) + { + return new Fp6(a.C0 - b.C0, a.C1 - b.C1, a.C2 - b.C2); + } + + public static Fp6 operator *(in Fp6 a, in Fp6 b) + { + // The intuition for this algorithm is that we can look at F_p^6 as a direct + // extension of F_p^2, and express the overall operations down to the base field + // F_p instead of only over F_p^2. This enables us to interleave multiplications + // and reductions, ensuring that we don't require double-width intermediate + // representations (with around twice as many limbs as F_p elements). + + // We want to express the multiplication c = a x b, where a = (a_0, a_1, a_2) is + // an element of F_p^6, and a_i = (a_i,0, a_i,1) is an element of F_p^2. The fully + // expanded multiplication is given by (2022-376 §5): + // + // c_0,0 = a_0,0 b_0,0 - a_0,1 b_0,1 + a_1,0 b_2,0 - a_1,1 b_2,1 + a_2,0 b_1,0 - a_2,1 b_1,1 + // - a_1,0 b_2,1 - a_1,1 b_2,0 - a_2,0 b_1,1 - a_2,1 b_1,0. + // = a_0,0 b_0,0 - a_0,1 b_0,1 + a_1,0 (b_2,0 - b_2,1) - a_1,1 (b_2,0 + b_2,1) + // + a_2,0 (b_1,0 - b_1,1) - a_2,1 (b_1,0 + b_1,1). + // + // c_0,1 = a_0,0 b_0,1 + a_0,1 b_0,0 + a_1,0 b_2,1 + a_1,1 b_2,0 + a_2,0 b_1,1 + a_2,1 b_1,0 + // + a_1,0 b_2,0 - a_1,1 b_2,1 + a_2,0 b_1,0 - a_2,1 b_1,1. + // = a_0,0 b_0,1 + a_0,1 b_0,0 + a_1,0(b_2,0 + b_2,1) + a_1,1(b_2,0 - b_2,1) + // + a_2,0(b_1,0 + b_1,1) + a_2,1(b_1,0 - b_1,1). + // + // c_1,0 = a_0,0 b_1,0 - a_0,1 b_1,1 + a_1,0 b_0,0 - a_1,1 b_0,1 + a_2,0 b_2,0 - a_2,1 b_2,1 + // - a_2,0 b_2,1 - a_2,1 b_2,0. + // = a_0,0 b_1,0 - a_0,1 b_1,1 + a_1,0 b_0,0 - a_1,1 b_0,1 + a_2,0(b_2,0 - b_2,1) + // - a_2,1(b_2,0 + b_2,1). + // + // c_1,1 = a_0,0 b_1,1 + a_0,1 b_1,0 + a_1,0 b_0,1 + a_1,1 b_0,0 + a_2,0 b_2,1 + a_2,1 b_2,0 + // + a_2,0 b_2,0 - a_2,1 b_2,1 + // = a_0,0 b_1,1 + a_0,1 b_1,0 + a_1,0 b_0,1 + a_1,1 b_0,0 + a_2,0(b_2,0 + b_2,1) + // + a_2,1(b_2,0 - b_2,1). + // + // c_2,0 = a_0,0 b_2,0 - a_0,1 b_2,1 + a_1,0 b_1,0 - a_1,1 b_1,1 + a_2,0 b_0,0 - a_2,1 b_0,1. + // c_2,1 = a_0,0 b_2,1 + a_0,1 b_2,0 + a_1,0 b_1,1 + a_1,1 b_1,0 + a_2,0 b_0,1 + a_2,1 b_0,0. + // + // Each of these is a "sum of products", which we can compute efficiently. + + var b10_p_b11 = b.C1.C0 + b.C1.C1; + var b10_m_b11 = b.C1.C0 - b.C1.C1; + var b20_p_b21 = b.C2.C0 + b.C2.C1; + var b20_m_b21 = b.C2.C0 - b.C2.C1; + + return new Fp6(new Fp2( + Fp.SumOfProducts( + stackalloc[] { a.C0.C0, -a.C0.C1, a.C1.C0, -a.C1.C1, a.C2.C0, -a.C2.C1 }, + stackalloc[] { b.C0.C0, b.C0.C1, b20_m_b21, b20_p_b21, b10_m_b11, b10_p_b11 } + ), + Fp.SumOfProducts( + stackalloc[] { a.C0.C0, a.C0.C1, a.C1.C0, a.C1.C1, a.C2.C0, a.C2.C1 }, + stackalloc[] { b.C0.C1, b.C0.C0, b20_p_b21, b20_m_b21, b10_p_b11, b10_m_b11 } + )), new Fp2( + Fp.SumOfProducts( + stackalloc[] { a.C0.C0, -a.C0.C1, a.C1.C0, -a.C1.C1, a.C2.C0, -a.C2.C1 }, + stackalloc[] { b.C1.C0, b.C1.C1, b.C0.C0, b.C0.C1, b20_m_b21, b20_p_b21 } + ), + Fp.SumOfProducts( + stackalloc[] { a.C0.C0, a.C0.C1, a.C1.C0, a.C1.C1, a.C2.C0, a.C2.C1 }, + stackalloc[] { b.C1.C1, b.C1.C0, b.C0.C1, b.C0.C0, b20_p_b21, b20_m_b21 } + )), new Fp2( + Fp.SumOfProducts( + stackalloc[] { a.C0.C0, -a.C0.C1, a.C1.C0, -a.C1.C1, a.C2.C0, -a.C2.C1 }, + stackalloc[] { b.C2.C0, b.C2.C1, b.C1.C0, b.C1.C1, b.C0.C0, b.C0.C1 } + ), + Fp.SumOfProducts( + stackalloc[] { a.C0.C0, a.C0.C1, a.C1.C0, a.C1.C1, a.C2.C0, a.C2.C1 }, + stackalloc[] { b.C2.C1, b.C2.C0, b.C1.C1, b.C1.C0, b.C0.C1, b.C0.C0 } + )) + ); + } + + #region Instance math methods + + public Fp6 Negate() => -this; + public Fp6 Multiply(in Fp6 value) => this * value; + public Fp6 Sum(in Fp6 value) => this + value; + public Fp6 Subtract(in Fp6 value) => this - value; + + #endregion +} diff --git a/src/Neo.Cryptography.BLS12_381/FpConstants.cs b/src/Neo.Cryptography.BLS12_381/FpConstants.cs new file mode 100644 index 0000000000..dd9f7ece9f --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/FpConstants.cs @@ -0,0 +1,84 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// FpConstants.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.Cryptography.BLS12_381; + +static class FpConstants +{ + // p = 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787 + public static readonly ulong[] MODULUS = + { + 0xb9fe_ffff_ffff_aaab, + 0x1eab_fffe_b153_ffff, + 0x6730_d2a0_f6b0_f624, + 0x6477_4b84_f385_12bf, + 0x4b1b_a7b6_434b_acd7, + 0x1a01_11ea_397f_e69a + }; + + // p - 2 + public static readonly ulong[] P_2 = + { + 0xb9fe_ffff_ffff_aaa9, + 0x1eab_fffe_b153_ffff, + 0x6730_d2a0_f6b0_f624, + 0x6477_4b84_f385_12bf, + 0x4b1b_a7b6_434b_acd7, + 0x1a01_11ea_397f_e69a + }; + + // (p + 1) / 4 + public static readonly ulong[] P_1_4 = + { + 0xee7f_bfff_ffff_eaab, + 0x07aa_ffff_ac54_ffff, + 0xd9cc_34a8_3dac_3d89, + 0xd91d_d2e1_3ce1_44af, + 0x92c6_e9ed_90d2_eb35, + 0x0680_447a_8e5f_f9a6 + }; + + // INV = -(p^{-1} mod 2^64) mod 2^64 + public const ulong INV = 0x89f3_fffc_fffc_fffd; + + // R = 2^384 mod p + public static readonly Fp R = Fp.FromRawUnchecked(new ulong[] + { + 0x7609_0000_0002_fffd, + 0xebf4_000b_c40c_0002, + 0x5f48_9857_53c7_58ba, + 0x77ce_5853_7052_5745, + 0x5c07_1a97_a256_ec6d, + 0x15f6_5ec3_fa80_e493 + }); + + // R2 = 2^(384*2) mod p + public static readonly Fp R2 = Fp.FromRawUnchecked(new ulong[] + { + 0xf4df_1f34_1c34_1746, + 0x0a76_e6a6_09d1_04f1, + 0x8de5_476c_4c95_b6d5, + 0x67eb_88a9_939d_83c0, + 0x9a79_3e85_b519_952d, + 0x1198_8fe5_92ca_e3aa + }); + + // R3 = 2^(384*3) mod p + public static readonly Fp R3 = Fp.FromRawUnchecked(new ulong[] + { + 0xed48_ac6b_d94c_a1e0, + 0x315f_831e_03a7_adf8, + 0x9a53_352a_615e_29dd, + 0x34c0_4e5e_921e_1761, + 0x2512_d435_6572_4728, + 0x0aa6_3460_9175_5d4d + }); +} diff --git a/src/Neo.Cryptography.BLS12_381/G1Affine.cs b/src/Neo.Cryptography.BLS12_381/G1Affine.cs new file mode 100644 index 0000000000..cbed66bc8f --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/G1Affine.cs @@ -0,0 +1,181 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// G1Affine.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Diagnostics.CodeAnalysis; +using static Neo.Cryptography.BLS12_381.ConstantTimeUtility; +using static Neo.Cryptography.BLS12_381.G1Constants; + +namespace Neo.Cryptography.BLS12_381; + +public readonly struct G1Affine : IEquatable +{ + public readonly Fp X; + public readonly Fp Y; + public readonly bool Infinity; + + public static readonly G1Affine Identity = new(in Fp.Zero, in Fp.One, true); + public static readonly G1Affine Generator = new(in GeneratorX, in GeneratorY, false); + + public bool IsIdentity => Infinity; + public bool IsTorsionFree => -new G1Projective(this).MulByX().MulByX() == new G1Projective(Endomorphism()); + public bool IsOnCurve => ((Y.Square() - (X.Square() * X)) == B) | Infinity; + + public G1Affine(in Fp x, in Fp y) + : this(in x, in y, false) + { + } + + private G1Affine(in Fp x, in Fp y, bool infinity) + { + X = x; + Y = y; + Infinity = infinity; + } + + public G1Affine(in G1Projective p) + { + bool s = p.Z.TryInvert(out Fp zinv); + + zinv = ConditionalSelect(in Fp.Zero, in zinv, s); + Fp x = p.X * zinv; + Fp y = p.Y * zinv; + + G1Affine tmp = new(in x, in y, false); + this = ConditionalSelect(in tmp, in Identity, !s); + } + + public static G1Affine FromUncompressed(ReadOnlySpan data) + { + return FromBytes(data, false, true); + } + + public static G1Affine FromCompressed(ReadOnlySpan data) + { + return FromBytes(data, true, true); + } + + private static G1Affine FromBytes(ReadOnlySpan data, bool compressed, bool check) + { + bool compression_flag_set = (data[0] & 0x80) != 0; + bool infinity_flag_set = (data[0] & 0x40) != 0; + bool sort_flag_set = (data[0] & 0x20) != 0; + byte[] tmp = data[0..48].ToArray(); + tmp[0] &= 0b0001_1111; + Fp x = Fp.FromBytes(tmp); + if (compressed) + { + Fp y = ((x.Square() * x) + B).Sqrt(); + y = ConditionalSelect(in y, -y, y.LexicographicallyLargest() ^ sort_flag_set); + G1Affine result = new(in x, in y, infinity_flag_set); + result = ConditionalSelect(in result, in Identity, infinity_flag_set); + if (check) + { + bool _checked = (!infinity_flag_set | (infinity_flag_set & !sort_flag_set & x.IsZero)) + & compression_flag_set; + _checked &= result.IsTorsionFree; + if (!_checked) throw new FormatException(); + } + return result; + } + else + { + Fp y = Fp.FromBytes(data[48..96]); + G1Affine result = ConditionalSelect(new(in x, in y, infinity_flag_set), in Identity, infinity_flag_set); + if (check) + { + bool _checked = (!infinity_flag_set | (infinity_flag_set & x.IsZero & y.IsZero)) + & !compression_flag_set + & !sort_flag_set; + _checked &= result.IsOnCurve & result.IsTorsionFree; + if (!_checked) throw new FormatException(); + } + return result; + } + } + + public static bool operator ==(in G1Affine a, in G1Affine b) + { + return (a.Infinity & b.Infinity) | (!a.Infinity & !b.Infinity & a.X == b.X & a.Y == b.Y); + } + + public static bool operator !=(in G1Affine a, in G1Affine b) + { + return !(a == b); + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj is not G1Affine other) return false; + return this == other; + } + + public bool Equals(G1Affine other) + { + return this == other; + } + + public override int GetHashCode() + { + if (Infinity) return Infinity.GetHashCode(); + return X.GetHashCode() ^ Y.GetHashCode(); + } + + public static G1Affine operator -(in G1Affine p) + { + return new G1Affine(in p.X, ConditionalSelect(-p.Y, in Fp.One, p.Infinity), p.Infinity); + } + + public byte[] ToCompressed() + { + byte[] res = ConditionalSelect(in X, in Fp.Zero, Infinity).ToArray(); + + // This point is in compressed form, so we set the most significant bit. + res[0] |= 0x80; + + // Is this point at infinity? If so, set the second-most significant bit. + res[0] |= ConditionalSelect((byte)0, (byte)0x40, Infinity); + + // Is the y-coordinate the lexicographically largest of the two associated with the + // x-coordinate? If so, set the third-most significant bit so long as this is not + // the point at infinity. + res[0] |= ConditionalSelect((byte)0, (byte)0x20, !Infinity & Y.LexicographicallyLargest()); + + return res; + } + + public byte[] ToUncompressed() + { + byte[] res = new byte[96]; + + ConditionalSelect(in X, in Fp.Zero, Infinity).TryWrite(res.AsSpan(0..48)); + ConditionalSelect(in Y, in Fp.Zero, Infinity).TryWrite(res.AsSpan(48..96)); + + // Is this point at infinity? If so, set the second-most significant bit. + res[0] |= ConditionalSelect((byte)0, (byte)0x40, Infinity); + + return res; + } + + public G1Projective ToCurve() + { + return new(this); + } + + private G1Affine Endomorphism() + { + return new(X * BETA, in Y, Infinity); + } + + public static G1Projective operator *(in G1Affine a, in Scalar b) + { + return new G1Projective(in a) * b.ToArray(); + } +} diff --git a/src/Neo.Cryptography.BLS12_381/G1Constants.cs b/src/Neo.Cryptography.BLS12_381/G1Constants.cs new file mode 100644 index 0000000000..1c07bb0900 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/G1Constants.cs @@ -0,0 +1,55 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// G1Constants.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.Cryptography.BLS12_381; + +static class G1Constants +{ + public static readonly Fp GeneratorX = Fp.FromRawUnchecked(new ulong[] + { + 0x5cb3_8790_fd53_0c16, + 0x7817_fc67_9976_fff5, + 0x154f_95c7_143b_a1c1, + 0xf0ae_6acd_f3d0_e747, + 0xedce_6ecc_21db_f440, + 0x1201_7741_9e0b_fb75 + }); + + public static readonly Fp GeneratorY = Fp.FromRawUnchecked(new ulong[] + { + 0xbaac_93d5_0ce7_2271, + 0x8c22_631a_7918_fd8e, + 0xdd59_5f13_5707_25ce, + 0x51ac_5829_5040_5194, + 0x0e1c_8c3f_ad00_59c0, + 0x0bbc_3efc_5008_a26a + }); + + public static readonly Fp B = Fp.FromRawUnchecked(new ulong[] + { + 0xaa27_0000_000c_fff3, + 0x53cc_0032_fc34_000a, + 0x478f_e97a_6b0a_807f, + 0xb1d3_7ebe_e6ba_24d7, + 0x8ec9_733b_bf78_ab2f, + 0x09d6_4551_3d83_de7e + }); + + public static readonly Fp BETA = Fp.FromRawUnchecked(new ulong[] + { + 0x30f1_361b_798a_64e8, + 0xf3b8_ddab_7ece_5a2a, + 0x16a8_ca3a_c615_77f7, + 0xc26a_2ff8_74fd_029b, + 0x3636_b766_6070_1c6e, + 0x051b_a4ab_241b_6160 + }); +} diff --git a/src/Neo.Cryptography.BLS12_381/G1Projective.cs b/src/Neo.Cryptography.BLS12_381/G1Projective.cs new file mode 100644 index 0000000000..95600af135 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/G1Projective.cs @@ -0,0 +1,294 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// G1Projective.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using static Neo.Cryptography.BLS12_381.Constants; +using static Neo.Cryptography.BLS12_381.ConstantTimeUtility; +using static Neo.Cryptography.BLS12_381.G1Constants; + +namespace Neo.Cryptography.BLS12_381; + +[StructLayout(LayoutKind.Explicit, Size = Fp.Size * 3)] +public readonly struct G1Projective : IEquatable +{ + [FieldOffset(0)] + public readonly Fp X; + [FieldOffset(Fp.Size)] + public readonly Fp Y; + [FieldOffset(Fp.Size * 2)] + public readonly Fp Z; + + public static readonly G1Projective Identity = new(in Fp.Zero, in Fp.One, in Fp.Zero); + public static readonly G1Projective Generator = new(in GeneratorX, in GeneratorY, in Fp.One); + + public bool IsIdentity => Z.IsZero; + public bool IsOnCurve => ((Y.Square() * Z) == (X.Square() * X + Z.Square() * Z * B)) | Z.IsZero; + + public G1Projective(in Fp x, in Fp y, in Fp z) + { + X = x; + Y = y; + Z = z; + } + + public G1Projective(in G1Affine p) + : this(in p.X, in p.Y, ConditionalSelect(in Fp.One, in Fp.Zero, p.Infinity)) + { + } + + public static bool operator ==(in G1Projective a, in G1Projective b) + { + // Is (xz, yz, z) equal to (x'z', y'z', z') when converted to affine? + + Fp x1 = a.X * b.Z; + Fp x2 = b.X * a.Z; + + Fp y1 = a.Y * b.Z; + Fp y2 = b.Y * a.Z; + + bool self_is_zero = a.Z.IsZero; + bool other_is_zero = b.Z.IsZero; + + // Both point at infinity. Or neither point at infinity, coordinates are the same. + return (self_is_zero & other_is_zero) | ((!self_is_zero) & (!other_is_zero) & x1 == x2 & y1 == y2); + } + + public static bool operator !=(in G1Projective a, in G1Projective b) + { + return !(a == b); + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj is not G1Projective other) return false; + return this == other; + } + + public bool Equals(G1Projective other) + { + return this == other; + } + + public override int GetHashCode() + { + return X.GetHashCode() ^ Y.GetHashCode() ^ Z.GetHashCode(); + } + + public static G1Projective operator -(in G1Projective p) + { + return new G1Projective(in p.X, -p.Y, in p.Z); + } + + private static Fp MulBy3B(in Fp a) + { + Fp b = a + a; + b += b; + return b + b + b; + } + + public G1Projective Double() + { + Fp t0 = Y.Square(); + Fp z3 = t0 + t0; + z3 += z3; + z3 += z3; + Fp t1 = Y * Z; + Fp t2 = Z.Square(); + t2 = MulBy3B(in t2); + Fp x3 = t2 * z3; + Fp y3 = t0 + t2; + z3 = t1 * z3; + t1 = t2 + t2; + t2 = t1 + t2; + t0 -= t2; + y3 = t0 * y3; + y3 = x3 + y3; + t1 = X * Y; + x3 = t0 * t1; + x3 += x3; + + G1Projective tmp = new(in x3, in y3, in z3); + return ConditionalSelect(in tmp, in Identity, IsIdentity); + } + + public static G1Projective operator +(in G1Projective a, in G1Projective b) + { + Fp t0 = a.X * b.X; + Fp t1 = a.Y * b.Y; + Fp t2 = a.Z * b.Z; + Fp t3 = a.X + a.Y; + Fp t4 = b.X + b.Y; + t3 *= t4; + t4 = t0 + t1; + t3 -= t4; + t4 = a.Y + a.Z; + Fp x3 = b.Y + b.Z; + t4 *= x3; + x3 = t1 + t2; + t4 -= x3; + x3 = a.X + a.Z; + Fp y3 = b.X + b.Z; + x3 *= y3; + y3 = t0 + t2; + y3 = x3 - y3; + x3 = t0 + t0; + t0 = x3 + t0; + t2 = MulBy3B(in t2); + Fp z3 = t1 + t2; + t1 -= t2; + y3 = MulBy3B(in y3); + x3 = t4 * y3; + t2 = t3 * t1; + x3 = t2 - x3; + y3 *= t0; + t1 *= z3; + y3 = t1 + y3; + t0 *= t3; + z3 *= t4; + z3 += t0; + + return new G1Projective(in x3, in y3, in z3); + } + + public static G1Projective operator +(in G1Projective a, in G1Affine b) + { + Fp t0 = a.X * b.X; + Fp t1 = a.Y * b.Y; + Fp t3 = b.X + b.Y; + Fp t4 = a.X + a.Y; + t3 *= t4; + t4 = t0 + t1; + t3 -= t4; + t4 = b.Y * a.Z; + t4 += a.Y; + Fp y3 = b.X * a.Z; + y3 += a.X; + Fp x3 = t0 + t0; + t0 = x3 + t0; + Fp t2 = MulBy3B(in a.Z); + Fp z3 = t1 + t2; + t1 -= t2; + y3 = MulBy3B(in y3); + x3 = t4 * y3; + t2 = t3 * t1; + x3 = t2 - x3; + y3 *= t0; + t1 *= z3; + y3 = t1 + y3; + t0 *= t3; + z3 *= t4; + z3 += t0; + + G1Projective tmp = new(in x3, in y3, in z3); + return ConditionalSelect(in tmp, in a, b.IsIdentity); + } + + public static G1Projective operator +(in G1Affine a, in G1Projective b) + { + return b + a; + } + + public static G1Projective operator -(in G1Projective a, in G1Projective b) + { + return a + -b; + } + + public static G1Projective operator -(in G1Projective a, in G1Affine b) + { + return a + -b; + } + + public static G1Projective operator -(in G1Affine a, in G1Projective b) + { + return -b + a; + } + + public static G1Projective operator *(in G1Projective a, byte[] b) + { + int length = b.Length; + if (length != 32) + throw new ArgumentException($"The argument {nameof(b)} must be 32 bytes."); + + G1Projective acc = Identity; + + foreach (bool bit in b + .SelectMany(p => Enumerable.Range(0, 8).Select(q => ((p >> q) & 1) == 1)) + .Reverse() + .Skip(1)) + { + acc = acc.Double(); + acc = ConditionalSelect(in acc, acc + a, bit); + } + + return acc; + } + + public static G1Projective operator *(in G1Projective a, in Scalar b) + { + return a * b.ToArray(); + } + + internal G1Projective MulByX() + { + G1Projective xself = Identity; + + ulong x = BLS_X >> 1; + G1Projective tmp = this; + while (x > 0) + { + tmp = tmp.Double(); + + if (x % 2 == 1) + { + xself += tmp; + } + x >>= 1; + } + + if (BLS_X_IS_NEGATIVE) + { + xself = -xself; + } + return xself; + } + + public G1Projective ClearCofactor() + { + return this - MulByX(); + } + + public static void BatchNormalize(ReadOnlySpan p, Span q) + { + int length = p.Length; + if (length != q.Length) + throw new ArgumentException($"{nameof(p)} and {nameof(q)} must have the same length."); + + Span x = stackalloc Fp[length]; + Fp acc = Fp.One; + for (int i = 0; i < length; i++) + { + x[i] = acc; + acc = ConditionalSelect(acc * p[i].Z, in acc, p[i].IsIdentity); + } + + acc = acc.Invert(); + + for (int i = length - 1; i >= 0; i--) + { + bool skip = p[i].IsIdentity; + Fp tmp = x[i] * acc; + acc = ConditionalSelect(acc * p[i].Z, in acc, skip); + G1Affine qi = new(p[i].X * tmp, p[i].Y * tmp); + q[i] = ConditionalSelect(in qi, in G1Affine.Identity, skip); + } + } +} diff --git a/src/Neo.Cryptography.BLS12_381/G2Affine.cs b/src/Neo.Cryptography.BLS12_381/G2Affine.cs new file mode 100644 index 0000000000..97256d02c7 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/G2Affine.cs @@ -0,0 +1,225 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// G2Affine.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Diagnostics.CodeAnalysis; +using static Neo.Cryptography.BLS12_381.ConstantTimeUtility; +using static Neo.Cryptography.BLS12_381.G2Constants; + +namespace Neo.Cryptography.BLS12_381; + +public readonly struct G2Affine : IEquatable +{ + public readonly Fp2 X; + public readonly Fp2 Y; + public readonly bool Infinity; + + public static readonly G2Affine Identity = new(in Fp2.Zero, in Fp2.One, true); + public static readonly G2Affine Generator = new(in GeneratorX, in GeneratorY, false); + + public bool IsIdentity => Infinity; + public bool IsTorsionFree + { + get + { + // Algorithm from Section 4 of https://eprint.iacr.org/2021/1130 + // Updated proof of correctness in https://eprint.iacr.org/2022/352 + // + // Check that psi(P) == [x] P + var p = new G2Projective(this); + return p.Psi() == p.MulByX(); + } + } + public bool IsOnCurve => ((Y.Square() - X.Square() * X) == B) | Infinity; // y^2 - x^3 ?= 4(u + 1) + + public G2Affine(in Fp2 x, in Fp2 y) + : this(in x, in y, false) + { + } + + private G2Affine(in Fp2 x, in Fp2 y, bool infinity) + { + X = x; + Y = y; + Infinity = infinity; + } + + public G2Affine(in G2Projective p) + { + bool s = p.Z.TryInvert(out Fp2 zinv); + + zinv = ConditionalSelect(in Fp2.Zero, in zinv, s); + Fp2 x = p.X * zinv; + Fp2 y = p.Y * zinv; + + G2Affine tmp = new(in x, in y, false); + this = ConditionalSelect(in tmp, in Identity, !s); + } + + public static bool operator ==(in G2Affine a, in G2Affine b) + { + // The only cases in which two points are equal are + // 1. infinity is set on both + // 2. infinity is not set on both, and their coordinates are equal + + return (a.Infinity & b.Infinity) | (!a.Infinity & !b.Infinity & a.X == b.X & a.Y == b.Y); + } + + public static bool operator !=(in G2Affine a, in G2Affine b) + { + return !(a == b); + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj is not G2Affine other) return false; + return this == other; + } + + public bool Equals(G2Affine other) + { + return this == other; + } + + public override int GetHashCode() + { + if (Infinity) return Infinity.GetHashCode(); + return X.GetHashCode() ^ Y.GetHashCode(); + } + + public static G2Affine operator -(in G2Affine a) + { + return new G2Affine( + in a.X, + ConditionalSelect(-a.Y, in Fp2.One, a.Infinity), + a.Infinity + ); + } + + public static G2Projective operator *(in G2Affine a, in Scalar b) + { + return new G2Projective(a) * b.ToArray(); + } + + public byte[] ToCompressed() + { + // Strictly speaking, self.x is zero already when self.infinity is true, but + // to guard against implementation mistakes we do not assume this. + var x = ConditionalSelect(in X, in Fp2.Zero, Infinity); + + var res = new byte[96]; + + x.C1.TryWrite(res.AsSpan(0..48)); + x.C0.TryWrite(res.AsSpan(48..96)); + + // This point is in compressed form, so we set the most significant bit. + res[0] |= 0x80; + + // Is this point at infinity? If so, set the second-most significant bit. + res[0] |= ConditionalSelect((byte)0, (byte)0x40, Infinity); + + // Is the y-coordinate the lexicographically largest of the two associated with the + // x-coordinate? If so, set the third-most significant bit so long as this is not + // the point at infinity. + res[0] |= ConditionalSelect((byte)0, (byte)0x20, !Infinity & Y.LexicographicallyLargest()); + + return res; + } + + public byte[] ToUncompressed() + { + var res = new byte[192]; + + var x = ConditionalSelect(in X, in Fp2.Zero, Infinity); + var y = ConditionalSelect(in Y, in Fp2.Zero, Infinity); + + x.C1.TryWrite(res.AsSpan(0..48)); + x.C0.TryWrite(res.AsSpan(48..96)); + y.C1.TryWrite(res.AsSpan(96..144)); + y.C0.TryWrite(res.AsSpan(144..192)); + + // Is this point at infinity? If so, set the second-most significant bit. + res[0] |= ConditionalSelect((byte)0, (byte)0x40, Infinity); + + return res; + } + + public static G2Affine FromUncompressed(ReadOnlySpan bytes) + { + return FromBytes(bytes, false, true); + } + + public static G2Affine FromCompressed(ReadOnlySpan bytes) + { + return FromBytes(bytes, true, true); + } + + private static G2Affine FromBytes(ReadOnlySpan bytes, bool compressed, bool check) + { + // Obtain the three flags from the start of the byte sequence + bool compression_flag_set = (bytes[0] & 0x80) != 0; + bool infinity_flag_set = (bytes[0] & 0x40) != 0; + bool sort_flag_set = (bytes[0] & 0x20) != 0; + + // Attempt to obtain the x-coordinate + var tmp = bytes[0..48].ToArray(); + tmp[0] &= 0b0001_1111; + var xc1 = Fp.FromBytes(tmp); + var xc0 = Fp.FromBytes(bytes[48..96]); + var x = new Fp2(in xc0, in xc1); + + if (compressed) + { + // Recover a y-coordinate given x by y = sqrt(x^3 + 4) + var y = ((x.Square() * x) + B).Sqrt(); + y = ConditionalSelect(in y, -y, y.LexicographicallyLargest() ^ sort_flag_set); + G2Affine result = new(in x, in y, infinity_flag_set); + result = ConditionalSelect(in result, in Identity, infinity_flag_set); + if (check) + { + bool _checked = (!infinity_flag_set | (infinity_flag_set & !sort_flag_set & x.IsZero)) + & compression_flag_set; + _checked &= result.IsTorsionFree; + if (!_checked) throw new FormatException(); + } + return result; + } + else + { + // Attempt to obtain the y-coordinate + var yc1 = Fp.FromBytes(bytes[96..144]); + var yc0 = Fp.FromBytes(bytes[144..192]); + var y = new Fp2(in yc0, in yc1); + + // Create a point representing this value + var p = ConditionalSelect(new G2Affine(in x, in y, infinity_flag_set), in Identity, infinity_flag_set); + + if (check) + { + bool _checked = + // If the infinity flag is set, the x and y coordinates should have been zero. + ((!infinity_flag_set) | (infinity_flag_set & x.IsZero & y.IsZero)) & + // The compression flag should not have been set, as this is an uncompressed element + (!compression_flag_set) & + // The sort flag should not have been set, as this is an uncompressed element + (!sort_flag_set); + _checked &= p.IsOnCurve & p.IsTorsionFree; + if (!_checked) throw new FormatException(); + } + + return p; + } + } + + public G2Projective ToCurve() + { + return new(this); + } +} diff --git a/src/Neo.Cryptography.BLS12_381/G2Constants.cs b/src/Neo.Cryptography.BLS12_381/G2Constants.cs new file mode 100644 index 0000000000..045ad43a93 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/G2Constants.cs @@ -0,0 +1,112 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// G2Constants.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.Cryptography.BLS12_381; + +static class G2Constants +{ + public static readonly Fp2 GeneratorX = new(Fp.FromRawUnchecked(new ulong[] + { + 0xf5f2_8fa2_0294_0a10, + 0xb3f5_fb26_87b4_961a, + 0xa1a8_93b5_3e2a_e580, + 0x9894_999d_1a3c_aee9, + 0x6f67_b763_1863_366b, + 0x0581_9192_4350_bcd7 + }), Fp.FromRawUnchecked(new ulong[] + { + 0xa5a9_c075_9e23_f606, + 0xaaa0_c59d_bccd_60c3, + 0x3bb1_7e18_e286_7806, + 0x1b1a_b6cc_8541_b367, + 0xc2b6_ed0e_f215_8547, + 0x1192_2a09_7360_edf3 + })); + + public static readonly Fp2 GeneratorY = new(Fp.FromRawUnchecked(new ulong[] + { + 0x4c73_0af8_6049_4c4a, + 0x597c_fa1f_5e36_9c5a, + 0xe7e6_856c_aa0a_635a, + 0xbbef_b5e9_6e0d_495f, + 0x07d3_a975_f0ef_25a2, + 0x0083_fd8e_7e80_dae5 + }), Fp.FromRawUnchecked(new ulong[] + { + 0xadc0_fc92_df64_b05d, + 0x18aa_270a_2b14_61dc, + 0x86ad_ac6a_3be4_eba0, + 0x7949_5c4e_c93d_a33a, + 0xe717_5850_a43c_caed, + 0x0b2b_c2a1_63de_1bf2 + })); + + public static readonly Fp2 B = new(Fp.FromRawUnchecked(new ulong[] + { + 0xaa27_0000_000c_fff3, + 0x53cc_0032_fc34_000a, + 0x478f_e97a_6b0a_807f, + 0xb1d3_7ebe_e6ba_24d7, + 0x8ec9_733b_bf78_ab2f, + 0x09d6_4551_3d83_de7e + }), Fp.FromRawUnchecked(new ulong[] + { + 0xaa27_0000_000c_fff3, + 0x53cc_0032_fc34_000a, + 0x478f_e97a_6b0a_807f, + 0xb1d3_7ebe_e6ba_24d7, + 0x8ec9_733b_bf78_ab2f, + 0x09d6_4551_3d83_de7e + })); + + public static readonly Fp2 B3 = B + B + B; + + // 1 / ((u+1) ^ ((q-1)/3)) + public static readonly Fp2 PsiCoeffX = new(in Fp.Zero, Fp.FromRawUnchecked(new ulong[] + { + 0x890dc9e4867545c3, + 0x2af322533285a5d5, + 0x50880866309b7e2c, + 0xa20d1b8c7e881024, + 0x14e4f04fe2db9068, + 0x14e56d3f1564853a + })); + + // 1 / ((u+1) ^ (p-1)/2) + public static readonly Fp2 PsiCoeffY = new(Fp.FromRawUnchecked(new ulong[] + { + 0x3e2f585da55c9ad1, + 0x4294213d86c18183, + 0x382844c88b623732, + 0x92ad2afd19103e18, + 0x1d794e4fac7cf0b9, + 0x0bd592fc7d825ec8 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x7bcfa7a25aa30fda, + 0xdc17dec12a927e7c, + 0x2f088dd86b4ebef1, + 0xd1ca2087da74d4a7, + 0x2da2596696cebc1d, + 0x0e2b7eedbbfd87d2 + })); + + // 1 / 2 ^ ((q-1)/3) + public static readonly Fp2 Psi2CoeffX = new(Fp.FromRawUnchecked(new ulong[] + { + 0xcd03c9e48671f071, + 0x5dab22461fcda5d2, + 0x587042afd3851b95, + 0x8eb60ebe01bacb9e, + 0x03f97d6e83d050d2, + 0x18f0206554638741 + }), in Fp.Zero); +} diff --git a/src/Neo.Cryptography.BLS12_381/G2Prepared.Adder.cs b/src/Neo.Cryptography.BLS12_381/G2Prepared.Adder.cs new file mode 100644 index 0000000000..c28b8889cb --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/G2Prepared.Adder.cs @@ -0,0 +1,74 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// G2Prepared.Adder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Runtime.CompilerServices; +using static Neo.Cryptography.BLS12_381.MillerLoopUtility; + +namespace Neo.Cryptography.BLS12_381; + +partial class G2Prepared +{ + class Adder : IMillerLoopDriver + { + public G2Projective Curve; + public readonly G2Affine Base; + public readonly List<(Fp2, Fp2, Fp2)> Coeffs; + + public Adder(in G2Affine q) + { + Curve = new G2Projective(in q); + Base = q; + Coeffs = new(68); + } + + object? IMillerLoopDriver.DoublingStep(in object? f) + { + var coeffs = DoublingStep(ref Curve); + Coeffs.Add(coeffs); + return null; + } + + object? IMillerLoopDriver.AdditionStep(in object? f) + { + var coeffs = AdditionStep(ref Curve, in Base); + Coeffs.Add(coeffs); + return null; + } + + #region IMillerLoopDriver + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object? Square(in object? f) => null; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static object? Conjugate(in object? f) => null; + + public static object? One + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => null; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + object? IMillerLoopDriver.Square(in object? f) => null; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + object? IMillerLoopDriver.Conjugate(in object? f) => null; + + object? IMillerLoopDriver.One + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => null; + } + + #endregion + } +} diff --git a/src/Neo.Cryptography.BLS12_381/G2Prepared.cs b/src/Neo.Cryptography.BLS12_381/G2Prepared.cs new file mode 100644 index 0000000000..38e8bdc902 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/G2Prepared.cs @@ -0,0 +1,31 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// G2Prepared.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using static Neo.Cryptography.BLS12_381.ConstantTimeUtility; +using static Neo.Cryptography.BLS12_381.MillerLoopUtility; + +namespace Neo.Cryptography.BLS12_381; + +partial class G2Prepared +{ + public readonly bool Infinity; + public readonly List<(Fp2, Fp2, Fp2)> Coeffs; + + public G2Prepared(in G2Affine q) + { + Infinity = q.IsIdentity; + var q2 = ConditionalSelect(in q, in G2Affine.Generator, Infinity); + var adder = new Adder(q2); + MillerLoop(adder); + Coeffs = adder.Coeffs; + if (Coeffs.Count != 68) throw new InvalidOperationException(); + } +} diff --git a/src/Neo.Cryptography.BLS12_381/G2Projective.cs b/src/Neo.Cryptography.BLS12_381/G2Projective.cs new file mode 100644 index 0000000000..08e0fd4593 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/G2Projective.cs @@ -0,0 +1,377 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// G2Projective.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Buffers.Binary; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using static Neo.Cryptography.BLS12_381.Constants; +using static Neo.Cryptography.BLS12_381.ConstantTimeUtility; +using static Neo.Cryptography.BLS12_381.G2Constants; + +namespace Neo.Cryptography.BLS12_381; + +[StructLayout(LayoutKind.Explicit, Size = Fp2.Size * 3)] +public readonly struct G2Projective : IEquatable +{ + [FieldOffset(0)] + public readonly Fp2 X; + [FieldOffset(Fp2.Size)] + public readonly Fp2 Y; + [FieldOffset(Fp2.Size * 2)] + public readonly Fp2 Z; + + public static readonly G2Projective Identity = new(in Fp2.Zero, in Fp2.One, in Fp2.Zero); + public static readonly G2Projective Generator = new(in GeneratorX, in GeneratorY, in Fp2.One); + + public bool IsIdentity => Z.IsZero; + public bool IsOnCurve => ((Y.Square() * Z) == (X.Square() * X + Z.Square() * Z * B)) | Z.IsZero; // Y^2 Z = X^3 + b Z^3 + + public G2Projective(in Fp2 x, in Fp2 y, in Fp2 z) + { + X = x; + Y = y; + Z = z; + } + + public G2Projective(in G2Affine p) + { + X = p.X; + Y = p.Y; + Z = ConditionalSelect(in Fp2.One, in Fp2.Zero, p.Infinity); + } + + public static bool operator ==(in G2Projective a, in G2Projective b) + { + // Is (xz, yz, z) equal to (x'z', y'z', z') when converted to affine? + + var x1 = a.X * b.Z; + var x2 = b.X * a.Z; + + var y1 = a.Y * b.Z; + var y2 = b.Y * a.Z; + + var self_is_zero = a.Z.IsZero; + var other_is_zero = b.Z.IsZero; + + return (self_is_zero & other_is_zero) // Both point at infinity + | ((!self_is_zero) & (!other_is_zero) & x1 == x2 & y1 == y2); + // Neither point at infinity, coordinates are the same + } + + public static bool operator !=(in G2Projective a, in G2Projective b) + { + return !(a == b); + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj is not G2Projective other) return false; + return this == other; + } + + public bool Equals(G2Projective other) + { + return this == other; + } + + public override int GetHashCode() + { + return X.GetHashCode() ^ Y.GetHashCode() ^ Z.GetHashCode(); + } + + public static G2Projective operator -(in G2Projective a) + { + return new(in a.X, -a.Y, in a.Z); + } + + public static G2Projective operator +(in G2Projective a, in G2Projective b) + { + // Algorithm 7, https://eprint.iacr.org/2015/1060.pdf + + var t0 = a.X * b.X; + var t1 = a.Y * b.Y; + var t2 = a.Z * b.Z; + var t3 = a.X + a.Y; + var t4 = b.X + b.Y; + t3 *= t4; + t4 = t0 + t1; + t3 -= t4; + t4 = a.Y + a.Z; + var x3 = b.Y + b.Z; + t4 *= x3; + x3 = t1 + t2; + t4 -= x3; + x3 = a.X + a.Z; + var y3 = b.X + b.Z; + x3 *= y3; + y3 = t0 + t2; + y3 = x3 - y3; + x3 = t0 + t0; + t0 = x3 + t0; + t2 = MulBy3B(t2); + var z3 = t1 + t2; + t1 -= t2; + y3 = MulBy3B(y3); + x3 = t4 * y3; + t2 = t3 * t1; + x3 = t2 - x3; + y3 *= t0; + t1 *= z3; + y3 = t1 + y3; + t0 *= t3; + z3 *= t4; + z3 += t0; + + return new G2Projective(in x3, in y3, in z3); + } + + public static G2Projective operator +(in G2Affine a, in G2Projective b) + { + return b + a; + } + + public static G2Projective operator +(in G2Projective a, in G2Affine b) + { + // Algorithm 8, https://eprint.iacr.org/2015/1060.pdf + + var t0 = a.X * b.X; + var t1 = a.Y * b.Y; + var t3 = b.X + b.Y; + var t4 = a.X + a.Y; + t3 *= t4; + t4 = t0 + t1; + t3 -= t4; + t4 = b.Y * a.Z; + t4 += a.Y; + var y3 = b.X * a.Z; + y3 += a.X; + var x3 = t0 + t0; + t0 = x3 + t0; + var t2 = MulBy3B(a.Z); + var z3 = t1 + t2; + t1 -= t2; + y3 = MulBy3B(y3); + x3 = t4 * y3; + t2 = t3 * t1; + x3 = t2 - x3; + y3 *= t0; + t1 *= z3; + y3 = t1 + y3; + t0 *= t3; + z3 *= t4; + z3 += t0; + + var tmp = new G2Projective(in x3, in y3, in z3); + + return ConditionalSelect(in tmp, in a, b.IsIdentity); + } + + public static G2Projective operator -(in G2Projective a, in G2Projective b) + { + return a + -b; + } + + public static G2Projective operator -(in G2Affine a, in G2Projective b) + { + return a + -b; + } + + public static G2Projective operator -(in G2Projective a, in G2Affine b) + { + return a + -b; + } + + public static G2Projective operator *(in G2Projective a, in Scalar b) + { + return a * b.ToArray(); + } + + public static G2Projective operator *(in G2Projective a, byte[] b) + { + var acc = Identity; + + // This is a simple double-and-add implementation of point + // multiplication, moving from most significant to least + // significant bit of the scalar. + // + // We skip the leading bit because it's always unset for Fq + // elements. + foreach (bool bit in b + .SelectMany(p => Enumerable.Range(0, 8).Select(q => ((p >> q) & 1) == 1)) + .Reverse() + .Skip(1)) + { + acc = acc.Double(); + acc = ConditionalSelect(in acc, acc + a, bit); + } + + return acc; + } + + private static Fp2 MulBy3B(Fp2 x) + { + return x * B3; + } + + public G2Projective Double() + { + // Algorithm 9, https://eprint.iacr.org/2015/1060.pdf + + var t0 = Y.Square(); + var z3 = t0 + t0; + z3 += z3; + z3 += z3; + var t1 = Y * Z; + var t2 = Z.Square(); + t2 = MulBy3B(t2); + var x3 = t2 * z3; + var y3 = t0 + t2; + z3 = t1 * z3; + t1 = t2 + t2; + t2 = t1 + t2; + t0 -= t2; + y3 = t0 * y3; + y3 = x3 + y3; + t1 = X * Y; + x3 = t0 * t1; + x3 += x3; + + var tmp = new G2Projective(in x3, in y3, in z3); + + return ConditionalSelect(in tmp, in Identity, IsIdentity); + } + + internal G2Projective Psi() + { + return new G2Projective( + // x = frobenius(x)/((u+1)^((p-1)/3)) + X.FrobeniusMap() * PsiCoeffX, + // y = frobenius(y)/(u+1)^((p-1)/2) + Y.FrobeniusMap() * PsiCoeffY, + // z = frobenius(z) + Z.FrobeniusMap() + ); + } + + internal G2Projective Psi2() + { + return new G2Projective( + // x = frobenius^2(x)/2^((p-1)/3); note that q^2 is the order of the field. + X * Psi2CoeffX, + // y = -frobenius^2(y); note that q^2 is the order of the field. + -Y, + // z = z + in Z + ); + } + + internal G2Projective MulByX() + { + var xself = Identity; + // NOTE: in BLS12-381 we can just skip the first bit. + var x = BLS_X >> 1; + var acc = this; + while (x != 0) + { + acc = acc.Double(); + if (x % 2 == 1) + { + xself += acc; + } + x >>= 1; + } + // finally, flip the sign + if (BLS_X_IS_NEGATIVE) + { + xself = -xself; + } + return xself; + } + + public G2Projective ClearCofactor() + { + var t1 = MulByX(); // [x] P + var t2 = Psi(); // psi(P) + + return Double().Psi2() // psi^2(2P) + + (t1 + t2).MulByX() // psi^2(2P) + [x^2] P + [x] psi(P) + - t1 // psi^2(2P) + [x^2 - x] P + [x] psi(P) + - t2 // psi^2(2P) + [x^2 - x] P + [x - 1] psi(P) + - this; // psi^2(2P) + [x^2 - x - 1] P + [x - 1] psi(P) + } + + public static void BatchNormalize(ReadOnlySpan p, Span q) + { + int length = p.Length; + if (length != q.Length) + throw new ArgumentException($"{nameof(p)} and {nameof(q)} must have the same length."); + + Span x = stackalloc Fp2[length]; + Fp2 acc = Fp2.One; + for (int i = 0; i < length; i++) + { + // We use the `x` field of `G2Affine` to store the product + // of previous z-coordinates seen. + x[i] = acc; + + // We will end up skipping all identities in p + acc = ConditionalSelect(acc * p[i].Z, in acc, p[i].IsIdentity); + } + + // This is the inverse, as all z-coordinates are nonzero and the ones + // that are not are skipped. + acc = acc.Invert(); + + for (int i = length - 1; i >= 0; i--) + { + bool skip = p[i].IsIdentity; + + // Compute tmp = 1/z + var tmp = x[i] * acc; + + // Cancel out z-coordinate in denominator of `acc` + acc = ConditionalSelect(acc * p[i].Z, in acc, skip); + + // Set the coordinates to the correct value + G2Affine qi = new(p[i].X * tmp, p[i].Y * tmp); + + q[i] = ConditionalSelect(in qi, in G2Affine.Identity, skip); + } + } + + public static G2Projective Random(RandomNumberGenerator rng) + { + Span buffer = stackalloc byte[sizeof(uint)]; + while (true) + { + var x = Fp2.Random(rng); + rng.GetBytes(buffer); + var flip_sign = BinaryPrimitives.ReadUInt32LittleEndian(buffer) % 2 != 0; + + // Obtain the corresponding y-coordinate given x as y = sqrt(x^3 + 4) + var y = ((x.Square() * x) + B).Sqrt(); + + G2Affine p; + try + { + p = new G2Affine(in x, flip_sign ? -y : y); + } + catch + { + continue; + } + + var result = p.ToCurve().ClearCofactor(); + if (!result.IsIdentity) return result; + } + } +} diff --git a/src/Neo.Cryptography.BLS12_381/GlobalSuppressions.cs b/src/Neo.Cryptography.BLS12_381/GlobalSuppressions.cs new file mode 100644 index 0000000000..97ff6376ff --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/GlobalSuppressions.cs @@ -0,0 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// GlobalSuppressions.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Usage", "CA2219")] diff --git a/src/Neo.Cryptography.BLS12_381/Gt.cs b/src/Neo.Cryptography.BLS12_381/Gt.cs new file mode 100644 index 0000000000..e6c33b18d5 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/Gt.cs @@ -0,0 +1,135 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Gt.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Security.Cryptography; +using static Neo.Cryptography.BLS12_381.ConstantTimeUtility; +using static Neo.Cryptography.BLS12_381.GtConstants; + +namespace Neo.Cryptography.BLS12_381; + +public readonly struct Gt : IEquatable +{ + public readonly Fp12 Value; + + public static readonly Gt Identity = new(in Fp12.One); + public static readonly Gt Generator = new(in GeneratorValue); + + public bool IsIdentity => this == Identity; + + public Gt(in Fp12 f) + { + Value = f; + } + + public static Gt FromBytes(ReadOnlySpan data) + { + return new(Fp12.FromBytes(data)); + } + + public static bool operator ==(in Gt a, in Gt b) + { + return a.Value == b.Value; + } + + public static bool operator !=(in Gt a, in Gt b) + { + return !(a == b); + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj is not Gt other) return false; + return this == other; + } + + public bool Equals(Gt other) + { + return this == other; + } + + public override int GetHashCode() + { + return Value.GetHashCode(); + } + + public byte[] ToArray() + { + return Value.ToArray(); + } + + public bool TryWrite(Span buffer) + { + return Value.TryWrite(buffer); + } + + public static Gt Random(RandomNumberGenerator rng) + { + while (true) + { + var inner = Fp12.Random(rng); + + // Not all elements of Fp12 are elements of the prime-order multiplicative + // subgroup. We run the random element through final_exponentiation to obtain + // a valid element, which requires that it is non-zero. + if (!inner.IsZero) + { + ref MillerLoopResult result = ref Unsafe.As(ref inner); + return result.FinalExponentiation(); + } + } + } + + public Gt Double() + { + return new(Value.Square()); + } + + public static Gt operator -(in Gt a) + { + // The element is unitary, so we just conjugate. + return new(a.Value.Conjugate()); + } + + public static Gt operator +(in Gt a, in Gt b) + { + return new(a.Value * b.Value); + } + + public static Gt operator -(in Gt a, in Gt b) + { + return a + -b; + } + + public static Gt operator *(in Gt a, in Scalar b) + { + var acc = Identity; + + // This is a simple double-and-add implementation of group element + // multiplication, moving from most significant to least + // significant bit of the scalar. + // + // We skip the leading bit because it's always unset for Fq + // elements. + foreach (bool bit in b + .ToArray() + .SelectMany(p => Enumerable.Range(0, 8).Select(q => ((p >> q) & 1) == 1)) + .Reverse() + .Skip(1)) + { + acc = acc.Double(); + acc = ConditionalSelect(in acc, acc + a, bit); + } + + return acc; + } +} diff --git a/src/Neo.Cryptography.BLS12_381/GtConstants.cs b/src/Neo.Cryptography.BLS12_381/GtConstants.cs new file mode 100644 index 0000000000..ca76083fe8 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/GtConstants.cs @@ -0,0 +1,115 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// GtConstants.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.Cryptography.BLS12_381; + +static class GtConstants +{ + // pairing(&G1Affine.generator(), &G2Affine.generator()) + public static readonly Fp12 GeneratorValue = new(new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x1972_e433_a01f_85c5, + 0x97d3_2b76_fd77_2538, + 0xc8ce_546f_c96b_cdf9, + 0xcef6_3e73_66d4_0614, + 0xa611_3427_8184_3780, + 0x13f3_448a_3fc6_d825 + }), Fp.FromRawUnchecked(new ulong[] + { + 0xd263_31b0_2e9d_6995, + 0x9d68_a482_f779_7e7d, + 0x9c9b_2924_8d39_ea92, + 0xf480_1ca2_e131_07aa, + 0xa16c_0732_bdbc_b066, + 0x083c_a4af_ba36_0478 + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x59e2_61db_0916_b641, + 0x2716_b6f4_b23e_960d, + 0xc8e5_5b10_a0bd_9c45, + 0x0bdb_0bd9_9c4d_eda8, + 0x8cf8_9ebf_57fd_aac5, + 0x12d6_b792_9e77_7a5e + }), Fp.FromRawUnchecked(new ulong[] + { + 0x5fc8_5188_b0e1_5f35, + 0x34a0_6e3a_8f09_6365, + 0xdb31_26a6_e02a_d62c, + 0xfc6f_5aa9_7d9a_990b, + 0xa12f_55f5_eb89_c210, + 0x1723_703a_926f_8889 + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x9358_8f29_7182_8778, + 0x43f6_5b86_11ab_7585, + 0x3183_aaf5_ec27_9fdf, + 0xfa73_d7e1_8ac9_9df6, + 0x64e1_76a6_a64c_99b0, + 0x179f_a78c_5838_8f1f + }), Fp.FromRawUnchecked(new ulong[] + { + 0x672a_0a11_ca2a_ef12, + 0x0d11_b9b5_2aa3_f16b, + 0xa444_12d0_699d_056e, + 0xc01d_0177_221a_5ba5, + 0x66e0_cede_6c73_5529, + 0x05f5_a71e_9fdd_c339 + }))), new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xd30a_88a1_b062_c679, + 0x5ac5_6a5d_35fc_8304, + 0xd0c8_34a6_a81f_290d, + 0xcd54_30c2_da37_07c7, + 0xf0c2_7ff7_8050_0af0, + 0x0924_5da6_e2d7_2eae + }), + Fp.FromRawUnchecked(new ulong[] + { + 0x9f2e_0676_791b_5156, + 0xe2d1_c823_4918_fe13, + 0x4c9e_459f_3c56_1bf4, + 0xa3e8_5e53_b9d3_e3c1, + 0x820a_121e_21a7_0020, + 0x15af_6183_41c5_9acc + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x7c95_658c_2499_3ab1, + 0x73eb_3872_1ca8_86b9, + 0x5256_d749_4774_34bc, + 0x8ba4_1902_ea50_4a8b, + 0x04a3_d3f8_0c86_ce6d, + 0x18a6_4a87_fb68_6eaa + }), Fp.FromRawUnchecked(new ulong[] + { + 0xbb83_e71b_b920_cf26, + 0x2a52_77ac_92a7_3945, + 0xfc0e_e59f_94f0_46a0, + 0x7158_cdf3_7860_58f7, + 0x7cc1_061b_82f9_45f6, + 0x03f8_47aa_9fdb_e567 + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x8078_dba5_6134_e657, + 0x1cd7_ec9a_4399_8a6e, + 0xb1aa_599a_1a99_3766, + 0xc9a0_f62f_0842_ee44, + 0x8e15_9be3_b605_dffa, + 0x0c86_ba0d_4af1_3fc2 + }), Fp.FromRawUnchecked(new ulong[] + { + 0xe80f_f2a0_6a52_ffb1, + 0x7694_ca48_721a_906c, + 0x7583_183e_03b0_8514, + 0xf567_afdd_40ce_e4e2, + 0x9a6d_96d2_e526_a5fc, + 0x197e_9f49_861f_2242 + })))); +} diff --git a/src/Neo.Cryptography.BLS12_381/IMillerLoopDriver.cs b/src/Neo.Cryptography.BLS12_381/IMillerLoopDriver.cs new file mode 100644 index 0000000000..0d0d3ac9df --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/IMillerLoopDriver.cs @@ -0,0 +1,21 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// IMillerLoopDriver.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.Cryptography.BLS12_381; + +interface IMillerLoopDriver +{ + public T DoublingStep(in T f); + public T AdditionStep(in T f); + public T Square(in T f); + public T Conjugate(in T f); + public T One { get; } +} diff --git a/src/Neo.Cryptography.BLS12_381/INumber.cs b/src/Neo.Cryptography.BLS12_381/INumber.cs new file mode 100644 index 0000000000..f328a77c2e --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/INumber.cs @@ -0,0 +1,70 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// INumber.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Runtime.CompilerServices; + +namespace Neo.Cryptography.BLS12_381; + +interface INumber where T : unmanaged, INumber +{ + //static abstract int Size { get; } + //static abstract ref readonly T Zero { get; } + //static abstract ref readonly T One { get; } + + //static abstract T operator -(in T x); + //static abstract T operator +(in T x, in T y); + //static abstract T operator -(in T x, in T y); + //static abstract T operator *(in T x, in T y); + + T Negate(); + T Sum(in T value); + T Subtract(in T value); + T Multiply(in T value); + + abstract T Square(); +} + +static class NumberExtensions +{ + private static T PowVartime(T one, T self, ulong[] by) where T : unmanaged, INumber + { + // Although this is labeled "vartime", it is only + // variable time with respect to the exponent. + var res = one; + for (int j = by.Length - 1; j >= 0; j--) + { + for (int i = 63; i >= 0; i--) + { + res = res.Square(); + if (((by[j] >> i) & 1) == 1) + { + res = res.Multiply(self); + } + } + } + return res; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Fp PowVartime(this Fp self, ulong[] by) => PowVartime(Fp.One, self, by); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Fp2 PowVartime(this Fp2 self, ulong[] by) => PowVartime(Fp2.One, self, by); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Fp6 PowVartime(this Fp6 self, ulong[] by) => PowVartime(Fp6.One, self, by); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Fp12 PowVartime(this Fp12 self, ulong[] by) => PowVartime(Fp12.One, self, by); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Scalar PowVartime(this Scalar self, ulong[] by) => PowVartime(Scalar.One, self, by); +} diff --git a/src/Neo.Cryptography.BLS12_381/MathUtility.cs b/src/Neo.Cryptography.BLS12_381/MathUtility.cs new file mode 100644 index 0000000000..3721b482cc --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/MathUtility.cs @@ -0,0 +1,67 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// MathUtility.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.Cryptography.BLS12_381; + +static class MathUtility +{ + public static (ulong result, ulong carry) Adc(ulong a, ulong b, ulong carry) + { + ulong result = unchecked(a + b + carry); + carry = ((a & b) | ((a | b) & (~result))) >> 63; + return (result, carry); + } + + public static (ulong result, ulong borrow) Sbb(ulong a, ulong b, ulong borrow) + { + ulong result = unchecked(a - b - borrow); + borrow = (((~a) & b) | (~(a ^ b)) & result) >> 63; + return (result, borrow); + } + + public static (ulong low, ulong high) Mac(ulong z, ulong x, ulong y, ulong carry) + { + ulong high = BigMul(x, y, out ulong low); + (low, carry) = Adc(low, carry, 0); + (high, _) = Adc(high, 0, carry); + (low, carry) = Adc(low, z, 0); + (high, _) = Adc(high, 0, carry); + return (low, high); + } + + /// Produces the full product of two unsigned 64-bit numbers. + /// The first number to multiply. + /// The second number to multiply. + /// The low 64-bit of the product of the specified numbers. + /// The high 64-bit of the product of the specified numbers. + public static ulong BigMul(ulong a, ulong b, out ulong low) + { + // Adaptation of algorithm for multiplication + // of 32-bit unsigned integers described + // in Hacker's Delight by Henry S. Warren, Jr. (ISBN 0-201-91465-4), Chapter 8 + // Basically, it's an optimized version of FOIL method applied to + // low and high dwords of each operand + + // Use 32-bit uints to optimize the fallback for 32-bit platforms. + uint al = (uint)a; + uint ah = (uint)(a >> 32); + uint bl = (uint)b; + uint bh = (uint)(b >> 32); + + ulong mull = ((ulong)al) * bl; + ulong t = ((ulong)ah) * bl + (mull >> 32); + ulong tl = ((ulong)al) * bh + (uint)t; + + low = tl << 32 | (uint)mull; + + return ((ulong)ah) * bh + (t >> 32) + (tl >> 32); + } +} diff --git a/src/Neo.Cryptography.BLS12_381/MillerLoopResult.cs b/src/Neo.Cryptography.BLS12_381/MillerLoopResult.cs new file mode 100644 index 0000000000..7f9bdfbb60 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/MillerLoopResult.cs @@ -0,0 +1,143 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// MillerLoopResult.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Runtime.InteropServices; +using static Neo.Cryptography.BLS12_381.Constants; + +namespace Neo.Cryptography.BLS12_381; + +[StructLayout(LayoutKind.Explicit, Size = Fp12.Size)] +readonly struct MillerLoopResult +{ + [FieldOffset(0)] + private readonly Fp12 v; + + public MillerLoopResult(in Fp12 v) + { + this.v = v; + } + + public Gt FinalExponentiation() + { + static (Fp2, Fp2) Fp4Square(in Fp2 a, in Fp2 b) + { + var t0 = a.Square(); + var t1 = b.Square(); + var t2 = t1.MulByNonresidue(); + var c0 = t2 + t0; + t2 = a + b; + t2 = t2.Square(); + t2 -= t0; + var c1 = t2 - t1; + + return (c0, c1); + } + + static Fp12 CyclotomicSquare(in Fp12 f) + { + var z0 = f.C0.C0; + var z4 = f.C0.C1; + var z3 = f.C0.C2; + var z2 = f.C1.C0; + var z1 = f.C1.C1; + var z5 = f.C1.C2; + + var (t0, t1) = Fp4Square(in z0, in z1); + + // For A + z0 = t0 - z0; + z0 = z0 + z0 + t0; + + z1 = t1 + z1; + z1 = z1 + z1 + t1; + + (t0, t1) = Fp4Square(in z2, in z3); + var (t2, t3) = Fp4Square(in z4, in z5); + + // For C + z4 = t0 - z4; + z4 = z4 + z4 + t0; + + z5 = t1 + z5; + z5 = z5 + z5 + t1; + + // For B + t0 = t3.MulByNonresidue(); + z2 = t0 + z2; + z2 = z2 + z2 + t0; + + z3 = t2 - z3; + z3 = z3 + z3 + t2; + + return new Fp12(new Fp6(in z0, in z4, in z3), new Fp6(in z2, in z1, in z5)); + } + + static Fp12 CycolotomicExp(in Fp12 f) + { + var x = BLS_X; + var tmp = Fp12.One; + var found_one = false; + foreach (bool i in Enumerable.Range(0, 64).Select(b => ((x >> b) & 1) == 1).Reverse()) + { + if (found_one) + tmp = CyclotomicSquare(tmp); + else + found_one = i; + + if (i) + tmp *= f; + } + + return tmp.Conjugate(); + } + + var f = v; + var t0 = f + .FrobeniusMap() + .FrobeniusMap() + .FrobeniusMap() + .FrobeniusMap() + .FrobeniusMap() + .FrobeniusMap(); + var t1 = f.Invert(); + var t2 = t0 * t1; + t1 = t2; + t2 = t2.FrobeniusMap().FrobeniusMap(); + t2 *= t1; + t1 = CyclotomicSquare(t2).Conjugate(); + var t3 = CycolotomicExp(t2); + var t4 = CyclotomicSquare(t3); + var t5 = t1 * t3; + t1 = CycolotomicExp(t5); + t0 = CycolotomicExp(t1); + var t6 = CycolotomicExp(t0); + t6 *= t4; + t4 = CycolotomicExp(t6); + t5 = t5.Conjugate(); + t4 *= t5 * t2; + t5 = t2.Conjugate(); + t1 *= t2; + t1 = t1.FrobeniusMap().FrobeniusMap().FrobeniusMap(); + t6 *= t5; + t6 = t6.FrobeniusMap(); + t3 *= t0; + t3 = t3.FrobeniusMap().FrobeniusMap(); + t3 *= t1; + t3 *= t6; + f = t3 * t4; + return new Gt(f); + } + + public static MillerLoopResult operator +(in MillerLoopResult a, in MillerLoopResult b) + { + return new(a.v * b.v); + } +} diff --git a/src/Neo.Cryptography.BLS12_381/MillerLoopUtility.cs b/src/Neo.Cryptography.BLS12_381/MillerLoopUtility.cs new file mode 100644 index 0000000000..03187b7944 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/MillerLoopUtility.cs @@ -0,0 +1,120 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// MillerLoopUtility.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using static Neo.Cryptography.BLS12_381.Constants; + +namespace Neo.Cryptography.BLS12_381; + +static class MillerLoopUtility +{ + public static T MillerLoop(D driver) where D : IMillerLoopDriver + { + var f = driver.One; + + var found_one = false; + foreach (var i in Enumerable.Range(0, 64).Reverse().Select(b => ((BLS_X >> 1 >> b) & 1) == 1)) + { + if (!found_one) + { + found_one = i; + continue; + } + + f = driver.DoublingStep(f); + + if (i) + f = driver.AdditionStep(f); + + f = driver.Square(f); + } + + f = driver.DoublingStep(f); + + if (BLS_X_IS_NEGATIVE) + f = driver.Conjugate(f); + + return f; + } + + public static Fp12 Ell(in Fp12 f, in (Fp2 X, Fp2 Y, Fp2 Z) coeffs, in G1Affine p) + { + var c0 = new Fp2(coeffs.X.C0 * p.Y, coeffs.X.C1 * p.Y); + var c1 = new Fp2(coeffs.Y.C0 * p.X, coeffs.Y.C1 * p.X); + return f.MulBy_014(in coeffs.Z, in c1, in c0); + } + + public static (Fp2, Fp2, Fp2) DoublingStep(ref G2Projective r) + { + // Adaptation of Algorithm 26, https://eprint.iacr.org/2010/354.pdf + var tmp0 = r.X.Square(); + var tmp1 = r.Y.Square(); + var tmp2 = tmp1.Square(); + var tmp3 = (tmp1 + r.X).Square() - tmp0 - tmp2; + tmp3 += tmp3; + var tmp4 = tmp0 + tmp0 + tmp0; + var tmp6 = r.X + tmp4; + var tmp5 = tmp4.Square(); + var zsquared = r.Z.Square(); + var x = tmp5 - tmp3 - tmp3; + var z = (r.Z + r.Y).Square() - tmp1 - zsquared; + var y = (tmp3 - x) * tmp4; + tmp2 += tmp2; + tmp2 += tmp2; + tmp2 += tmp2; + y -= tmp2; + r = new(in x, in y, in z); + tmp3 = tmp4 * zsquared; + tmp3 += tmp3; + tmp3 = -tmp3; + tmp6 = tmp6.Square() - tmp0 - tmp5; + tmp1 += tmp1; + tmp1 += tmp1; + tmp6 -= tmp1; + tmp0 = r.Z * zsquared; + tmp0 += tmp0; + + return (tmp0, tmp3, tmp6); + } + + public static (Fp2, Fp2, Fp2) AdditionStep(ref G2Projective r, in G2Affine q) + { + // Adaptation of Algorithm 27, https://eprint.iacr.org/2010/354.pdf + var zsquared = r.Z.Square(); + var ysquared = q.Y.Square(); + var t0 = zsquared * q.X; + var t1 = ((q.Y + r.Z).Square() - ysquared - zsquared) * zsquared; + var t2 = t0 - r.X; + var t3 = t2.Square(); + var t4 = t3 + t3; + t4 += t4; + var t5 = t4 * t2; + var t6 = t1 - r.Y - r.Y; + var t9 = t6 * q.X; + var t7 = t4 * r.X; + var x = t6.Square() - t5 - t7 - t7; + var z = (r.Z + t2).Square() - zsquared - t3; + var t10 = q.Y + z; + var t8 = (t7 - x) * t6; + t0 = r.Y * t5; + t0 += t0; + var y = t8 - t0; + r = new(in x, in y, in z); + t10 = t10.Square() - ysquared; + var ztsquared = r.Z.Square(); + t10 -= ztsquared; + t9 = t9 + t9 - t10; + t10 = r.Z + r.Z; + t6 = -t6; + t1 = t6 + t6; + + return (t10, t1, t9); + } +} diff --git a/src/Neo.Cryptography.BLS12_381/Neo.Cryptography.BLS12_381.csproj b/src/Neo.Cryptography.BLS12_381/Neo.Cryptography.BLS12_381.csproj new file mode 100644 index 0000000000..f74c9f1ec0 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/Neo.Cryptography.BLS12_381.csproj @@ -0,0 +1,16 @@ + + + + 0.3.0 + netstandard2.1;net8.0 + enable + enable + Neo.Cryptography.BLS12_381 + ../../bin/$(PackageId) + + + + + + + diff --git a/src/Neo.Cryptography.BLS12_381/Properties/AssemblyInfo.cs b/src/Neo.Cryptography.BLS12_381/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..998a53f179 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/Properties/AssemblyInfo.cs @@ -0,0 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// AssemblyInfo.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Neo.Cryptography.BLS12_381.Tests")] diff --git a/src/Neo.Cryptography.BLS12_381/Scalar.cs b/src/Neo.Cryptography.BLS12_381/Scalar.cs new file mode 100644 index 0000000000..ef40e9b817 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/Scalar.cs @@ -0,0 +1,513 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Scalar.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Text; +using static Neo.Cryptography.BLS12_381.ConstantTimeUtility; +using static Neo.Cryptography.BLS12_381.MathUtility; +using static Neo.Cryptography.BLS12_381.ScalarConstants; + +namespace Neo.Cryptography.BLS12_381; + +[StructLayout(LayoutKind.Explicit, Size = Size)] +public readonly struct Scalar : IEquatable, INumber +{ + public const int Size = 32; + public const int SizeL = Size / sizeof(ulong); + public static readonly Scalar Default = new(); + + public static ref readonly Scalar Zero => ref Default; + public static ref readonly Scalar One => ref R; + + public bool IsZero => this == Zero; + + internal Scalar(ulong[] values) + { + if (values.Length != SizeL) + throw new FormatException($"The argument `{nameof(values)}` must contain {SizeL} entries."); + + // This internal method is only used by the constants classes. + // The data must be in the correct format. + // So, there is no need to do any additional checks. + this = Unsafe.As(ref MemoryMarshal.GetReference(MemoryMarshal.Cast(values))); + } + + public Scalar(ulong value) + { + Span data = stackalloc ulong[SizeL]; + data[0] = value; + this = FromRaw(data); + } + + public Scalar(RandomNumberGenerator rng) + { + Span buffer = stackalloc byte[Size * 2]; + rng.GetBytes(buffer); + this = FromBytesWide(buffer); + } + + public static Scalar FromBytes(ReadOnlySpan data) + { + if (data.Length != Size) + throw new FormatException($"The argument `{nameof(data)}` must contain {Size} bytes."); + + ref readonly Scalar ref_ = ref Unsafe.As(ref MemoryMarshal.GetReference(data)); + + try + { + return ref_ * R2; + } + finally + { + ReadOnlySpan u64 = MemoryMarshal.Cast(data); + ulong borrow = 0; + (_, borrow) = Sbb(u64[0], MODULUS_LIMBS_64[0], borrow); + (_, borrow) = Sbb(u64[1], MODULUS_LIMBS_64[1], borrow); + (_, borrow) = Sbb(u64[2], MODULUS_LIMBS_64[2], borrow); + (_, borrow) = Sbb(u64[3], MODULUS_LIMBS_64[3], borrow); + if (borrow == 0) + { + // If the element is smaller than MODULUS then the subtraction will underflow. + // Otherwise, throws. + // Why not throw before return? + // Because we want to run the method in a constant time. + throw new FormatException(); + } + } + } + + public static Scalar FromBytesWide(ReadOnlySpan data) + { + if (data.Length != Size * 2) + throw new FormatException($"The argument `{nameof(data)}` must contain {Size * 2} bytes."); + + ReadOnlySpan d = MemoryMarshal.Cast(data); + return d[0] * R2 + d[1] * R3; + } + + public static Scalar FromRaw(ReadOnlySpan data) + { + if (data.Length != SizeL) + throw new FormatException($"The argument `{nameof(data)}` must contain {SizeL} entries."); + + ReadOnlySpan span = MemoryMarshal.Cast(data); + return span[0] * R2; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ReadOnlySpan GetSpan() + { + return MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in this), 1)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Span GetSpanU64() + { + return MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref Unsafe.AsRef(in this), 1)); + } + + public override string ToString() + { + byte[] bytes = ToArray(); + StringBuilder sb = new(); + sb.Append("0x"); + for (int i = bytes.Length - 1; i >= 0; i--) + sb.AppendFormat("{0:x2}", bytes[i]); + return sb.ToString(); + } + + public static bool operator ==(in Scalar a, in Scalar b) + { + return ConstantTimeEq(in a, in b); + } + + public static bool operator !=(in Scalar a, in Scalar b) + { + return !(a == b); + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + if (obj is not Scalar other) return false; + return this == other; + } + + public bool Equals(Scalar other) + { + return this == other; + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + + public Scalar Double() + { + return this + this; + } + + public byte[] ToArray() + { + ReadOnlySpan self = GetSpanU64(); + + // Turn into canonical form by computing + // (a.R) / R = a + Scalar result = MontgomeryReduce(self[0], self[1], self[2], self[3], 0, 0, 0, 0); + return result.GetSpan().ToArray(); + } + + public Scalar Square() + { + ReadOnlySpan self = GetSpanU64(); + ulong r0, r1, r2, r3, r4, r5, r6, r7; + ulong carry; + + (r1, carry) = Mac(0, self[0], self[1], 0); + (r2, carry) = Mac(0, self[0], self[2], carry); + (r3, r4) = Mac(0, self[0], self[3], carry); + + (r3, carry) = Mac(r3, self[1], self[2], 0); + (r4, r5) = Mac(r4, self[1], self[3], carry); + + (r5, r6) = Mac(r5, self[2], self[3], 0); + + r7 = r6 >> 63; + r6 = (r6 << 1) | (r5 >> 63); + r5 = (r5 << 1) | (r4 >> 63); + r4 = (r4 << 1) | (r3 >> 63); + r3 = (r3 << 1) | (r2 >> 63); + r2 = (r2 << 1) | (r1 >> 63); + r1 <<= 1; + + (r0, carry) = Mac(0, self[0], self[0], 0); + (r1, carry) = Adc(r1, carry, 0); + (r2, carry) = Mac(r2, self[1], self[1], carry); + (r3, carry) = Adc(r3, carry, 0); + (r4, carry) = Mac(r4, self[2], self[2], carry); + (r5, carry) = Adc(r5, carry, 0); + (r6, carry) = Mac(r6, self[3], self[3], carry); + (r7, _) = Adc(r7, carry, 0); + + return MontgomeryReduce(r0, r1, r2, r3, r4, r5, r6, r7); + } + + public Scalar Sqrt() + { + // Tonelli-Shank's algorithm for q mod 16 = 1 + // https://eprint.iacr.org/2012/685.pdf (page 12, algorithm 5) + + // w = self^((t - 1) // 2) + // = self^6104339283789297388802252303364915521546564123189034618274734669823 + var w = this.PowVartime(new ulong[] + { + 0x7fff_2dff_7fff_ffff, + 0x04d0_ec02_a9de_d201, + 0x94ce_bea4_199c_ec04, + 0x0000_0000_39f6_d3a9 + }); + + var v = S; + var x = this * w; + var b = x * w; + + // Initialize z as the 2^S root of unity. + var z = ROOT_OF_UNITY; + + for (uint max_v = S; max_v >= 1; max_v--) + { + uint k = 1; + var tmp = b.Square(); + var j_less_than_v = true; + + for (uint j = 2; j < max_v; j++) + { + var tmp_is_one = tmp == One; + var squared = ConditionalSelect(in tmp, in z, tmp_is_one).Square(); + tmp = ConditionalSelect(in squared, in tmp, tmp_is_one); + var new_z = ConditionalSelect(in z, in squared, tmp_is_one); + j_less_than_v &= j != v; + k = ConditionalSelect(j, k, tmp_is_one); + z = ConditionalSelect(in z, in new_z, j_less_than_v); + } + + var result = x * z; + x = ConditionalSelect(in result, in x, b == One); + z = z.Square(); + b *= z; + v = k; + } + + if (x * x != this) throw new ArithmeticException(); + return x; + } + + public Scalar Pow(ulong[] by) + { + if (by.Length != SizeL) + throw new ArgumentException($"The length of the parameter `{nameof(by)}` must be {SizeL}."); + + var res = One; + for (int j = by.Length - 1; j >= 0; j--) + { + for (int i = 63; i >= 0; i--) + { + res = res.Square(); + var tmp = res; + tmp *= this; + res.ConditionalAssign(in tmp, ((by[j] >> i) & 1) == 1); + } + } + return res; + } + + public Scalar Invert() + { + static void SquareAssignMulti(ref Scalar n, int num_times) + { + for (int i = 0; i < num_times; i++) + { + n = n.Square(); + } + } + + var t0 = Square(); + var t1 = t0 * this; + var t16 = t0.Square(); + var t6 = t16.Square(); + var t5 = t6 * t0; + t0 = t6 * t16; + var t12 = t5 * t16; + var t2 = t6.Square(); + var t7 = t5 * t6; + var t15 = t0 * t5; + var t17 = t12.Square(); + t1 *= t17; + var t3 = t7 * t2; + var t8 = t1 * t17; + var t4 = t8 * t2; + var t9 = t8 * t7; + t7 = t4 * t5; + var t11 = t4 * t17; + t5 = t9 * t17; + var t14 = t7 * t15; + var t13 = t11 * t12; + t12 = t11 * t17; + t15 *= t12; + t16 *= t15; + t3 *= t16; + t17 *= t3; + t0 *= t17; + t6 *= t0; + t2 *= t6; + SquareAssignMulti(ref t0, 8); + t0 *= t17; + SquareAssignMulti(ref t0, 9); + t0 *= t16; + SquareAssignMulti(ref t0, 9); + t0 *= t15; + SquareAssignMulti(ref t0, 9); + t0 *= t15; + SquareAssignMulti(ref t0, 7); + t0 *= t14; + SquareAssignMulti(ref t0, 7); + t0 *= t13; + SquareAssignMulti(ref t0, 10); + t0 *= t12; + SquareAssignMulti(ref t0, 9); + t0 *= t11; + SquareAssignMulti(ref t0, 8); + t0 *= t8; + SquareAssignMulti(ref t0, 8); + t0 *= this; + SquareAssignMulti(ref t0, 14); + t0 *= t9; + SquareAssignMulti(ref t0, 10); + t0 *= t8; + SquareAssignMulti(ref t0, 15); + t0 *= t7; + SquareAssignMulti(ref t0, 10); + t0 *= t6; + SquareAssignMulti(ref t0, 8); + t0 *= t5; + SquareAssignMulti(ref t0, 16); + t0 *= t3; + SquareAssignMulti(ref t0, 8); + t0 *= t2; + SquareAssignMulti(ref t0, 7); + t0 *= t4; + SquareAssignMulti(ref t0, 9); + t0 *= t2; + SquareAssignMulti(ref t0, 8); + t0 *= t3; + SquareAssignMulti(ref t0, 8); + t0 *= t2; + SquareAssignMulti(ref t0, 8); + t0 *= t2; + SquareAssignMulti(ref t0, 8); + t0 *= t2; + SquareAssignMulti(ref t0, 8); + t0 *= t3; + SquareAssignMulti(ref t0, 8); + t0 *= t2; + SquareAssignMulti(ref t0, 8); + t0 *= t2; + SquareAssignMulti(ref t0, 5); + t0 *= t1; + SquareAssignMulti(ref t0, 5); + t0 *= t1; + + if (this == Zero) throw new DivideByZeroException(); + return t0; + } + + private static Scalar MontgomeryReduce(ulong r0, ulong r1, ulong r2, ulong r3, ulong r4, ulong r5, ulong r6, ulong r7) + { + // The Montgomery reduction here is based on Algorithm 14.32 in + // Handbook of Applied Cryptography + // . + + ulong carry, carry2; + + var k = unchecked(r0 * INV); + (_, carry) = Mac(r0, k, MODULUS_LIMBS_64[0], 0); + (r1, carry) = Mac(r1, k, MODULUS_LIMBS_64[1], carry); + (r2, carry) = Mac(r2, k, MODULUS_LIMBS_64[2], carry); + (r3, carry) = Mac(r3, k, MODULUS_LIMBS_64[3], carry); + (r4, carry2) = Adc(r4, 0, carry); + + k = unchecked(r1 * INV); + (_, carry) = Mac(r1, k, MODULUS_LIMBS_64[0], 0); + (r2, carry) = Mac(r2, k, MODULUS_LIMBS_64[1], carry); + (r3, carry) = Mac(r3, k, MODULUS_LIMBS_64[2], carry); + (r4, carry) = Mac(r4, k, MODULUS_LIMBS_64[3], carry); + (r5, carry2) = Adc(r5, carry2, carry); + + k = unchecked(r2 * INV); + (_, carry) = Mac(r2, k, MODULUS_LIMBS_64[0], 0); + (r3, carry) = Mac(r3, k, MODULUS_LIMBS_64[1], carry); + (r4, carry) = Mac(r4, k, MODULUS_LIMBS_64[2], carry); + (r5, carry) = Mac(r5, k, MODULUS_LIMBS_64[3], carry); + (r6, carry2) = Adc(r6, carry2, carry); + + k = unchecked(r3 * INV); + (_, carry) = Mac(r3, k, MODULUS_LIMBS_64[0], 0); + (r4, carry) = Mac(r4, k, MODULUS_LIMBS_64[1], carry); + (r5, carry) = Mac(r5, k, MODULUS_LIMBS_64[2], carry); + (r6, carry) = Mac(r6, k, MODULUS_LIMBS_64[3], carry); + (r7, _) = Adc(r7, carry2, carry); + + // Result may be within MODULUS of the correct value + ReadOnlySpan tmp = stackalloc[] { r4, r5, r6, r7 }; + return MemoryMarshal.Cast(tmp)[0] - MODULUS; + } + + public static Scalar operator *(in Scalar a, in Scalar b) + { + ReadOnlySpan self = a.GetSpanU64(), rhs = b.GetSpanU64(); + ulong r0, r1, r2, r3, r4, r5, r6, r7; + ulong carry; + + (r0, carry) = Mac(0, self[0], rhs[0], 0); + (r1, carry) = Mac(0, self[0], rhs[1], carry); + (r2, carry) = Mac(0, self[0], rhs[2], carry); + (r3, r4) = Mac(0, self[0], rhs[3], carry); + + (r1, carry) = Mac(r1, self[1], rhs[0], 0); + (r2, carry) = Mac(r2, self[1], rhs[1], carry); + (r3, carry) = Mac(r3, self[1], rhs[2], carry); + (r4, r5) = Mac(r4, self[1], rhs[3], carry); + + (r2, carry) = Mac(r2, self[2], rhs[0], 0); + (r3, carry) = Mac(r3, self[2], rhs[1], carry); + (r4, carry) = Mac(r4, self[2], rhs[2], carry); + (r5, r6) = Mac(r5, self[2], rhs[3], carry); + + (r3, carry) = Mac(r3, self[3], rhs[0], 0); + (r4, carry) = Mac(r4, self[3], rhs[1], carry); + (r5, carry) = Mac(r5, self[3], rhs[2], carry); + (r6, r7) = Mac(r6, self[3], rhs[3], carry); + + return MontgomeryReduce(r0, r1, r2, r3, r4, r5, r6, r7); + } + + public static Scalar operator -(in Scalar a, in Scalar b) + { + ReadOnlySpan self = a.GetSpanU64(), rhs = b.GetSpanU64(); + ulong d0, d1, d2, d3; + ulong carry, borrow; + + (d0, borrow) = Sbb(self[0], rhs[0], 0); + (d1, borrow) = Sbb(self[1], rhs[1], borrow); + (d2, borrow) = Sbb(self[2], rhs[2], borrow); + (d3, borrow) = Sbb(self[3], rhs[3], borrow); + + borrow = borrow == 0 ? ulong.MinValue : ulong.MaxValue; + (d0, carry) = Adc(d0, MODULUS_LIMBS_64[0] & borrow, 0); + (d1, carry) = Adc(d1, MODULUS_LIMBS_64[1] & borrow, carry); + (d2, carry) = Adc(d2, MODULUS_LIMBS_64[2] & borrow, carry); + (d3, _) = Adc(d3, MODULUS_LIMBS_64[3] & borrow, carry); + + ReadOnlySpan tmp = stackalloc[] { d0, d1, d2, d3 }; + return MemoryMarshal.Cast(tmp)[0]; + } + + public static Scalar operator +(in Scalar a, in Scalar b) + { + ReadOnlySpan self = a.GetSpanU64(), rhs = b.GetSpanU64(); + ulong d0, d1, d2, d3; + ulong carry; + + (d0, carry) = Adc(self[0], rhs[0], 0); + (d1, carry) = Adc(self[1], rhs[1], carry); + (d2, carry) = Adc(self[2], rhs[2], carry); + (d3, _) = Adc(self[3], rhs[3], carry); + + // Attempt to subtract the modulus, to ensure the value + // is smaller than the modulus. + ReadOnlySpan tmp = stackalloc[] { d0, d1, d2, d3 }; + return MemoryMarshal.Cast(tmp)[0] - MODULUS; + } + + public static Scalar operator -(in Scalar a) + { + ReadOnlySpan self = a.GetSpanU64(); + ulong d0, d1, d2, d3; + ulong borrow; + + // Subtract `self` from `MODULUS` to negate. Ignore the final + // borrow because it cannot underflow; self is guaranteed to + // be in the field. + (d0, borrow) = Sbb(MODULUS_LIMBS_64[0], self[0], 0); + (d1, borrow) = Sbb(MODULUS_LIMBS_64[1], self[1], borrow); + (d2, borrow) = Sbb(MODULUS_LIMBS_64[2], self[2], borrow); + (d3, _) = Sbb(MODULUS_LIMBS_64[3], self[3], borrow); + + // `tmp` could be `MODULUS` if `self` was zero. Create a mask that is + // zero if `self` was zero, and `u64::max_value()` if self was nonzero. + ulong mask = a.IsZero ? ulong.MinValue : ulong.MaxValue; + + ReadOnlySpan tmp = stackalloc[] { d0 & mask, d1 & mask, d2 & mask, d3 & mask }; + return MemoryMarshal.Cast(tmp)[0]; + } + + #region Instance math methods + + public Scalar Negate() => -this; + public Scalar Multiply(in Scalar value) => this * value; + public Scalar Sum(in Scalar value) => this + value; + public Scalar Subtract(in Scalar value) => this - value; + + #endregion +} diff --git a/src/Neo.Cryptography.BLS12_381/ScalarConstants.cs b/src/Neo.Cryptography.BLS12_381/ScalarConstants.cs new file mode 100644 index 0000000000..56bab5b067 --- /dev/null +++ b/src/Neo.Cryptography.BLS12_381/ScalarConstants.cs @@ -0,0 +1,96 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ScalarConstants.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.Cryptography.BLS12_381; + +static class ScalarConstants +{ + // The modulus as u32 limbs. + public static readonly uint[] MODULUS_LIMBS_32 = + { + 0x0000_0001, + 0xffff_ffff, + 0xfffe_5bfe, + 0x53bd_a402, + 0x09a1_d805, + 0x3339_d808, + 0x299d_7d48, + 0x73ed_a753 + }; + + // The modulus as u64 limbs. + public static readonly ulong[] MODULUS_LIMBS_64 = + { + 0xffff_ffff_0000_0001, + 0x53bd_a402_fffe_5bfe, + 0x3339_d808_09a1_d805, + 0x73ed_a753_299d_7d48 + }; + + // q = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001 + public static readonly Scalar MODULUS = new(MODULUS_LIMBS_64); + + // The number of bits needed to represent the modulus. + public const uint MODULUS_BITS = 255; + + // GENERATOR = 7 (multiplicative generator of r-1 order, that is also quadratic nonresidue) + public static readonly Scalar GENERATOR = new(new ulong[] + { + 0x0000_000e_ffff_fff1, + 0x17e3_63d3_0018_9c0f, + 0xff9c_5787_6f84_57b0, + 0x3513_3220_8fc5_a8c4 + }); + + // INV = -(q^{-1} mod 2^64) mod 2^64 + public const ulong INV = 0xffff_fffe_ffff_ffff; + + // R = 2^256 mod q + public static readonly Scalar R = new(new ulong[] + { + 0x0000_0001_ffff_fffe, + 0x5884_b7fa_0003_4802, + 0x998c_4fef_ecbc_4ff5, + 0x1824_b159_acc5_056f + }); + + // R^2 = 2^512 mod q + public static readonly Scalar R2 = new(new ulong[] + { + 0xc999_e990_f3f2_9c6d, + 0x2b6c_edcb_8792_5c23, + 0x05d3_1496_7254_398f, + 0x0748_d9d9_9f59_ff11 + }); + + // R^3 = 2^768 mod q + public static readonly Scalar R3 = new(new ulong[] + { + 0xc62c_1807_439b_73af, + 0x1b3e_0d18_8cf0_6990, + 0x73d1_3c71_c7b5_f418, + 0x6e2a_5bb9_c8db_33e9 + }); + + // 2^S * t = MODULUS - 1 with t odd + public const uint S = 32; + + // GENERATOR^t where t * 2^s + 1 = q with t odd. + // In other words, this is a 2^s root of unity. + // `GENERATOR = 7 mod q` is a generator of the q - 1 order multiplicative subgroup. + public static readonly Scalar ROOT_OF_UNITY = new(new ulong[] + { + 0xb9b5_8d8c_5f0e_466a, + 0x5b1b_4c80_1819_d7ec, + 0x0af5_3ae3_52a3_1e64, + 0x5bf3_adda_19e9_b27b + }); +} diff --git a/src/Neo/LogLevel.cs b/src/Neo.Extensions/LogLevel.cs similarity index 73% rename from src/Neo/LogLevel.cs rename to src/Neo.Extensions/LogLevel.cs index ab794a1b18..5e3acca37c 100644 --- a/src/Neo/LogLevel.cs +++ b/src/Neo.Extensions/LogLevel.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// LogLevel.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo.Extensions/Neo.Extensions.csproj b/src/Neo.Extensions/Neo.Extensions.csproj new file mode 100644 index 0000000000..a7f4870568 --- /dev/null +++ b/src/Neo.Extensions/Neo.Extensions.csproj @@ -0,0 +1,21 @@ + + + + netstandard2.1;net8.0 + enable + Neo.Extensions + NEO;Blockchain;Extensions + ../../bin/$(PackageId) + + + + + + + + + + + + + diff --git a/src/Neo/Utility.cs b/src/Neo.Extensions/Utility.cs similarity index 81% rename from src/Neo/Utility.cs rename to src/Neo.Extensions/Utility.cs index c159a54924..fca164a4f2 100644 --- a/src/Neo/Utility.cs +++ b/src/Neo.Extensions/Utility.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// Utility.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -30,7 +31,7 @@ public Logger() } } - public static event LogEventHandler Logging; + public static event LogEventHandler? Logging; /// /// A strict UTF8 encoding used in NEO system. diff --git a/src/Neo.GUI/GUI/BulkPayDialog.Designer.cs b/src/Neo.GUI/GUI/BulkPayDialog.Designer.cs new file mode 100644 index 0000000000..12af8811ff --- /dev/null +++ b/src/Neo.GUI/GUI/BulkPayDialog.Designer.cs @@ -0,0 +1,129 @@ +// Copyright (C) 2016-2024 The Neo Project. +// +// The neo-gui is free software distributed under the MIT software +// license, see the accompanying file LICENSE in the main directory of +// the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.GUI +{ + partial class BulkPayDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(BulkPayDialog)); + this.textBox3 = new System.Windows.Forms.TextBox(); + this.label4 = new System.Windows.Forms.Label(); + this.comboBox1 = new System.Windows.Forms.ComboBox(); + this.label3 = new System.Windows.Forms.Label(); + this.button1 = new System.Windows.Forms.Button(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.groupBox1.SuspendLayout(); + this.SuspendLayout(); + // + // textBox3 + // + resources.ApplyResources(this.textBox3, "textBox3"); + this.textBox3.Name = "textBox3"; + this.textBox3.ReadOnly = true; + // + // label4 + // + resources.ApplyResources(this.label4, "label4"); + this.label4.Name = "label4"; + // + // comboBox1 + // + resources.ApplyResources(this.comboBox1, "comboBox1"); + this.comboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboBox1.FormattingEnabled = true; + this.comboBox1.Name = "comboBox1"; + this.comboBox1.SelectedIndexChanged += new System.EventHandler(this.comboBox1_SelectedIndexChanged); + // + // label3 + // + resources.ApplyResources(this.label3, "label3"); + this.label3.Name = "label3"; + // + // button1 + // + resources.ApplyResources(this.button1, "button1"); + this.button1.DialogResult = System.Windows.Forms.DialogResult.OK; + this.button1.Name = "button1"; + this.button1.UseVisualStyleBackColor = true; + // + // groupBox1 + // + resources.ApplyResources(this.groupBox1, "groupBox1"); + this.groupBox1.Controls.Add(this.textBox1); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.TabStop = false; + // + // textBox1 + // + this.textBox1.AcceptsReturn = true; + resources.ApplyResources(this.textBox1, "textBox1"); + this.textBox1.Name = "textBox1"; + this.textBox1.TextChanged += new System.EventHandler(this.textBox1_TextChanged); + // + // BulkPayDialog + // + resources.ApplyResources(this, "$this"); + this.AcceptButton = this.button1; + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.textBox3); + this.Controls.Add(this.label4); + this.Controls.Add(this.comboBox1); + this.Controls.Add(this.label3); + this.Controls.Add(this.button1); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "BulkPayDialog"; + this.ShowInTaskbar = false; + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TextBox textBox3; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.ComboBox comboBox1; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.TextBox textBox1; + } +} diff --git a/src/Neo.GUI/GUI/BulkPayDialog.cs b/src/Neo.GUI/GUI/BulkPayDialog.cs new file mode 100644 index 0000000000..b67f9ae61c --- /dev/null +++ b/src/Neo.GUI/GUI/BulkPayDialog.cs @@ -0,0 +1,81 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// BulkPayDialog.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Wallets; +using System; +using System.Linq; +using System.Windows.Forms; +using static Neo.Program; + +namespace Neo.GUI +{ + internal partial class BulkPayDialog : Form + { + public BulkPayDialog(AssetDescriptor asset = null) + { + InitializeComponent(); + if (asset == null) + { + foreach (UInt160 assetId in NEP5Watched) + { + try + { + comboBox1.Items.Add(new AssetDescriptor(Service.NeoSystem.StoreView, Service.NeoSystem.Settings, assetId)); + } + catch (ArgumentException) + { + continue; + } + } + } + else + { + comboBox1.Items.Add(asset); + comboBox1.SelectedIndex = 0; + comboBox1.Enabled = false; + } + } + + public TxOutListBoxItem[] GetOutputs() + { + AssetDescriptor asset = (AssetDescriptor)comboBox1.SelectedItem; + return textBox1.Lines.Where(p => !string.IsNullOrWhiteSpace(p)).Select(p => + { + string[] line = p.Split(new[] { ' ', '\t', ',' }, StringSplitOptions.RemoveEmptyEntries); + return new TxOutListBoxItem + { + AssetName = asset.AssetName, + AssetId = asset.AssetId, + Value = BigDecimal.Parse(line[1], asset.Decimals), + ScriptHash = line[0].ToScriptHash(Service.NeoSystem.Settings.AddressVersion) + }; + }).Where(p => p.Value.Value != 0).ToArray(); + } + + private void comboBox1_SelectedIndexChanged(object sender, EventArgs e) + { + if (comboBox1.SelectedItem is AssetDescriptor asset) + { + textBox3.Text = Service.CurrentWallet.GetAvailable(Service.NeoSystem.StoreView, asset.AssetId).ToString(); + } + else + { + textBox3.Text = ""; + } + textBox1_TextChanged(this, EventArgs.Empty); + } + + private void textBox1_TextChanged(object sender, EventArgs e) + { + button1.Enabled = comboBox1.SelectedIndex >= 0 && textBox1.TextLength > 0; + } + } +} diff --git a/src/Neo.GUI/GUI/BulkPayDialog.es-ES.resx b/src/Neo.GUI/GUI/BulkPayDialog.es-ES.resx new file mode 100644 index 0000000000..3aa43a7bae --- /dev/null +++ b/src/Neo.GUI/GUI/BulkPayDialog.es-ES.resx @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + 24, 54 + + + 44, 17 + + + Saldo: + + + 22, 17 + + + 46, 17 + + + Activo: + + + Aceptar + + + Pagar a + + + Pago + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/BulkPayDialog.resx b/src/Neo.GUI/GUI/BulkPayDialog.resx new file mode 100644 index 0000000000..0a6c0c3d28 --- /dev/null +++ b/src/Neo.GUI/GUI/BulkPayDialog.resx @@ -0,0 +1,354 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Top, Left, Right + + + + 74, 51 + + + 468, 23 + + + + 12 + + + textBox3 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 1 + + + True + + + NoControl + + + 12, 54 + + + 56, 17 + + + 11 + + + Balance: + + + label4 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 2 + + + Top, Left, Right + + + 74, 14 + + + 468, 25 + + + 10 + + + comboBox1 + + + System.Windows.Forms.ComboBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 3 + + + True + + + NoControl + + + 26, 17 + + + 42, 17 + + + 9 + + + Asset: + + + label3 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 4 + + + Bottom, Right + + + False + + + NoControl + + + 467, 325 + + + 75, 23 + + + 17 + + + OK + + + button1 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 5 + + + Top, Bottom, Left, Right + + + Fill + + + Consolas, 9pt + + + 3, 19 + + + True + + + Vertical + + + 524, 217 + + + 0 + + + False + + + textBox1 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox1 + + + 0 + + + 12, 80 + + + 530, 239 + + + 18 + + + Pay to + + + groupBox1 + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 0 + + + True + + + 7, 17 + + + 554, 360 + + + 微软雅黑, 9pt + + + 3, 4, 3, 4 + + + CenterScreen + + + Payment + + + BulkPayDialog + + + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/BulkPayDialog.zh-Hans.resx b/src/Neo.GUI/GUI/BulkPayDialog.zh-Hans.resx new file mode 100644 index 0000000000..e429e3bf5e --- /dev/null +++ b/src/Neo.GUI/GUI/BulkPayDialog.zh-Hans.resx @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + 62, 51 + + + 480, 23 + + + 44, 17 + + + 余额: + + + 62, 14 + + + 480, 25 + + + 12, 17 + + + 44, 17 + + + 资产: + + + 确定 + + + 账户和金额 + + + 支付 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/ChangePasswordDialog.Designer.cs b/src/Neo.GUI/GUI/ChangePasswordDialog.Designer.cs new file mode 100644 index 0000000000..bcf044ecc9 --- /dev/null +++ b/src/Neo.GUI/GUI/ChangePasswordDialog.Designer.cs @@ -0,0 +1,137 @@ +// Copyright (C) 2016-2024 The Neo Project. +// +// The neo-gui is free software distributed under the MIT software +// license, see the accompanying file LICENSE in the main directory of +// the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.GUI +{ + partial class ChangePasswordDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ChangePasswordDialog)); + this.label1 = new System.Windows.Forms.Label(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.label2 = new System.Windows.Forms.Label(); + this.textBox2 = new System.Windows.Forms.TextBox(); + this.label3 = new System.Windows.Forms.Label(); + this.textBox3 = new System.Windows.Forms.TextBox(); + this.button1 = new System.Windows.Forms.Button(); + this.button2 = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // label1 + // + resources.ApplyResources(this.label1, "label1"); + this.label1.Name = "label1"; + // + // textBox1 + // + resources.ApplyResources(this.textBox1, "textBox1"); + this.textBox1.Name = "textBox1"; + this.textBox1.UseSystemPasswordChar = true; + this.textBox1.TextChanged += new System.EventHandler(this.textBox_TextChanged); + // + // label2 + // + resources.ApplyResources(this.label2, "label2"); + this.label2.Name = "label2"; + // + // textBox2 + // + resources.ApplyResources(this.textBox2, "textBox2"); + this.textBox2.Name = "textBox2"; + this.textBox2.UseSystemPasswordChar = true; + this.textBox2.TextChanged += new System.EventHandler(this.textBox_TextChanged); + // + // label3 + // + resources.ApplyResources(this.label3, "label3"); + this.label3.Name = "label3"; + // + // textBox3 + // + resources.ApplyResources(this.textBox3, "textBox3"); + this.textBox3.Name = "textBox3"; + this.textBox3.UseSystemPasswordChar = true; + this.textBox3.TextChanged += new System.EventHandler(this.textBox_TextChanged); + // + // button1 + // + resources.ApplyResources(this.button1, "button1"); + this.button1.DialogResult = System.Windows.Forms.DialogResult.OK; + this.button1.Name = "button1"; + this.button1.UseVisualStyleBackColor = true; + // + // button2 + // + resources.ApplyResources(this.button2, "button2"); + this.button2.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.button2.Name = "button2"; + this.button2.UseVisualStyleBackColor = true; + // + // ChangePasswordDialog + // + this.AcceptButton = this.button1; + resources.ApplyResources(this, "$this"); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.button2; + this.Controls.Add(this.button2); + this.Controls.Add(this.button1); + this.Controls.Add(this.textBox3); + this.Controls.Add(this.label3); + this.Controls.Add(this.textBox2); + this.Controls.Add(this.label2); + this.Controls.Add(this.textBox1); + this.Controls.Add(this.label1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "ChangePasswordDialog"; + this.ShowInTaskbar = false; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.TextBox textBox2; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.TextBox textBox3; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.Button button2; + } +} diff --git a/src/Neo.GUI/GUI/ChangePasswordDialog.cs b/src/Neo.GUI/GUI/ChangePasswordDialog.cs new file mode 100644 index 0000000000..f6ea079e41 --- /dev/null +++ b/src/Neo.GUI/GUI/ChangePasswordDialog.cs @@ -0,0 +1,54 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ChangePasswordDialog.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Windows.Forms; + +namespace Neo.GUI +{ + internal partial class ChangePasswordDialog : Form + { + public string OldPassword + { + get + { + return textBox1.Text; + } + set + { + textBox1.Text = value; + } + } + + public string NewPassword + { + get + { + return textBox2.Text; + } + set + { + textBox2.Text = value; + textBox3.Text = value; + } + } + + public ChangePasswordDialog() + { + InitializeComponent(); + } + + private void textBox_TextChanged(object sender, EventArgs e) + { + button1.Enabled = textBox1.TextLength > 0 && textBox2.TextLength > 0 && textBox3.Text == textBox2.Text; + } + } +} diff --git a/src/Neo.GUI/GUI/ChangePasswordDialog.es-ES.resx b/src/Neo.GUI/GUI/ChangePasswordDialog.es-ES.resx new file mode 100644 index 0000000000..27c58de2d0 --- /dev/null +++ b/src/Neo.GUI/GUI/ChangePasswordDialog.es-ES.resx @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + 55, 15 + + + 115, 17 + + + Contraseña actual: + + + 177, 12 + + + 275, 23 + + + 55, 44 + + + 116, 17 + + + Nueva contraseña: + + + 177, 41 + + + 275, 23 + + + 159, 17 + + + Repetir nueva contraseña: + + + 177, 70 + + + 275, 23 + + + 296, 107 + + + Aceptar + + + 377, 107 + + + Cancelar + + + 464, 142 + + + Cambiar contraseña + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/ChangePasswordDialog.resx b/src/Neo.GUI/GUI/ChangePasswordDialog.resx new file mode 100644 index 0000000000..89c4851043 --- /dev/null +++ b/src/Neo.GUI/GUI/ChangePasswordDialog.resx @@ -0,0 +1,369 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + True + + + + 41, 15 + + + 92, 17 + + + 0 + + + Old Password: + + + label1 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 7 + + + + Top, Left, Right + + + 139, 12 + + + 234, 23 + + + 1 + + + textBox1 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 6 + + + True + + + NoControl + + + 36, 44 + + + 97, 17 + + + 2 + + + New Password: + + + label2 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 5 + + + Top, Left, Right + + + 139, 41 + + + 234, 23 + + + 3 + + + textBox2 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 4 + + + True + + + NoControl + + + 12, 73 + + + 121, 17 + + + 4 + + + Re-Enter Password: + + + label3 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 3 + + + Top, Left, Right + + + 139, 70 + + + 234, 23 + + + 5 + + + textBox3 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 2 + + + Bottom, Right + + + False + + + 217, 107 + + + 75, 23 + + + 6 + + + OK + + + button1 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 1 + + + Bottom, Right + + + NoControl + + + 298, 107 + + + 75, 23 + + + 7 + + + Cancel + + + button2 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 0 + + + True + + + 7, 17 + + + 385, 142 + + + Microsoft YaHei UI, 9pt + + + 2, 2, 2, 2 + + + CenterScreen + + + Change Password + + + ChangePasswordDialog + + + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/ChangePasswordDialog.zh-Hans.resx b/src/Neo.GUI/GUI/ChangePasswordDialog.zh-Hans.resx new file mode 100644 index 0000000000..9ec5cb724c --- /dev/null +++ b/src/Neo.GUI/GUI/ChangePasswordDialog.zh-Hans.resx @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + 24, 15 + + + 47, 17 + + + 旧密码: + + + 77, 12 + + + 296, 23 + + + 24, 44 + + + 47, 17 + + + 新密码: + + + 77, 41 + + + 296, 23 + + + 59, 17 + + + 重复密码: + + + 77, 70 + + + 296, 23 + + + 确定 + + + 取消 + + + 修改密码 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/ConsoleForm.Designer.cs b/src/Neo.GUI/GUI/ConsoleForm.Designer.cs new file mode 100644 index 0000000000..b93f503075 --- /dev/null +++ b/src/Neo.GUI/GUI/ConsoleForm.Designer.cs @@ -0,0 +1,91 @@ +// Copyright (C) 2016-2024 The Neo Project. +// +// The neo-gui is free software distributed under the MIT software +// license, see the accompanying file LICENSE in the main directory of +// the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.GUI +{ + partial class ConsoleForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.textBox1 = new System.Windows.Forms.TextBox(); + this.textBox2 = new System.Windows.Forms.TextBox(); + this.SuspendLayout(); + // + // textBox1 + // + this.textBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.textBox1.Location = new System.Drawing.Point(12, 12); + this.textBox1.Font = new System.Drawing.Font("Consolas", 11.0f); + this.textBox1.MaxLength = 1048576; + this.textBox1.Multiline = true; + this.textBox1.Name = "textBox1"; + this.textBox1.ReadOnly = true; + this.textBox1.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.textBox1.Size = new System.Drawing.Size(609, 367); + this.textBox1.TabIndex = 1; + // + // textBox2 + // + this.textBox2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.textBox2.Location = new System.Drawing.Point(12, 385); + this.textBox2.Font = new System.Drawing.Font("Consolas", 11.0f); + this.textBox2.Name = "textBox2"; + this.textBox2.Size = new System.Drawing.Size(609, 21); + this.textBox2.TabIndex = 0; + this.textBox2.KeyDown += new System.Windows.Forms.KeyEventHandler(this.textBox2_KeyDown); + // + // ConsoleForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(633, 418); + this.Controls.Add(this.textBox2); + this.Controls.Add(this.textBox1); + this.Name = "ConsoleForm"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "Console"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.TextBox textBox2; + } +} diff --git a/src/Neo.GUI/GUI/ConsoleForm.cs b/src/Neo.GUI/GUI/ConsoleForm.cs new file mode 100644 index 0000000000..ae1543980e --- /dev/null +++ b/src/Neo.GUI/GUI/ConsoleForm.cs @@ -0,0 +1,69 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ConsoleForm.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.ConsoleService; +using System; +using System.IO; +using System.Threading; +using System.Windows.Forms; + +namespace Neo.GUI +{ + internal partial class ConsoleForm : Form + { + private Thread thread; + private readonly QueueReader queue = new QueueReader(); + + public ConsoleForm() + { + InitializeComponent(); + } + + protected override void OnHandleCreated(EventArgs e) + { + base.OnHandleCreated(e); + Console.SetOut(new TextBoxWriter(textBox1)); + Console.SetIn(queue); + thread = new Thread(Program.Service.RunConsole); + thread.Start(); + } + + protected override void OnFormClosing(FormClosingEventArgs e) + { + queue.Enqueue($"exit{Environment.NewLine}"); + thread.Join(); + Console.SetIn(new StreamReader(Console.OpenStandardInput())); + Console.SetOut(new StreamWriter(Console.OpenStandardOutput())); + base.OnFormClosing(e); + } + + private void textBox2_KeyDown(object sender, KeyEventArgs e) + { + if (e.KeyCode == Keys.Enter) + { + e.SuppressKeyPress = true; + string line = $"{textBox2.Text}{Environment.NewLine}"; + textBox1.AppendText(ConsoleHelper.ReadingPassword ? "***" : line); + switch (textBox2.Text.ToLower()) + { + case "clear": + textBox1.Clear(); + break; + case "exit": + Close(); + return; + } + queue.Enqueue(line); + textBox2.Clear(); + } + } + } +} diff --git a/src/Neo.GUI/GUI/ConsoleForm.resx b/src/Neo.GUI/GUI/ConsoleForm.resx new file mode 100644 index 0000000000..1af7de150c --- /dev/null +++ b/src/Neo.GUI/GUI/ConsoleForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/CreateMultiSigContractDialog.Designer.cs b/src/Neo.GUI/GUI/CreateMultiSigContractDialog.Designer.cs new file mode 100644 index 0000000000..1fa9aba600 --- /dev/null +++ b/src/Neo.GUI/GUI/CreateMultiSigContractDialog.Designer.cs @@ -0,0 +1,153 @@ +// Copyright (C) 2016-2024 The Neo Project. +// +// The neo-gui is free software distributed under the MIT software +// license, see the accompanying file LICENSE in the main directory of +// the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.GUI +{ + partial class CreateMultiSigContractDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(CreateMultiSigContractDialog)); + this.button5 = new System.Windows.Forms.Button(); + this.button4 = new System.Windows.Forms.Button(); + this.textBox5 = new System.Windows.Forms.TextBox(); + this.label7 = new System.Windows.Forms.Label(); + this.listBox1 = new System.Windows.Forms.ListBox(); + this.numericUpDown2 = new System.Windows.Forms.NumericUpDown(); + this.label6 = new System.Windows.Forms.Label(); + this.button6 = new System.Windows.Forms.Button(); + this.button1 = new System.Windows.Forms.Button(); + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown2)).BeginInit(); + this.SuspendLayout(); + // + // button5 + // + resources.ApplyResources(this.button5, "button5"); + this.button5.Name = "button5"; + this.button5.UseVisualStyleBackColor = true; + this.button5.Click += new System.EventHandler(this.button5_Click); + // + // button4 + // + resources.ApplyResources(this.button4, "button4"); + this.button4.Name = "button4"; + this.button4.UseVisualStyleBackColor = true; + this.button4.Click += new System.EventHandler(this.button4_Click); + // + // textBox5 + // + resources.ApplyResources(this.textBox5, "textBox5"); + this.textBox5.Name = "textBox5"; + this.textBox5.TextChanged += new System.EventHandler(this.textBox5_TextChanged); + // + // label7 + // + resources.ApplyResources(this.label7, "label7"); + this.label7.Name = "label7"; + // + // listBox1 + // + resources.ApplyResources(this.listBox1, "listBox1"); + this.listBox1.FormattingEnabled = true; + this.listBox1.Name = "listBox1"; + this.listBox1.SelectedIndexChanged += new System.EventHandler(this.listBox1_SelectedIndexChanged); + // + // numericUpDown2 + // + resources.ApplyResources(this.numericUpDown2, "numericUpDown2"); + this.numericUpDown2.Maximum = new decimal(new int[] { + 0, + 0, + 0, + 0}); + this.numericUpDown2.Name = "numericUpDown2"; + this.numericUpDown2.ValueChanged += new System.EventHandler(this.numericUpDown2_ValueChanged); + // + // label6 + // + resources.ApplyResources(this.label6, "label6"); + this.label6.Name = "label6"; + // + // button6 + // + resources.ApplyResources(this.button6, "button6"); + this.button6.DialogResult = System.Windows.Forms.DialogResult.OK; + this.button6.Name = "button6"; + this.button6.UseVisualStyleBackColor = true; + // + // button1 + // + resources.ApplyResources(this.button1, "button1"); + this.button1.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.button1.Name = "button1"; + this.button1.UseVisualStyleBackColor = true; + // + // CreateMultiSigContractDialog + // + this.AcceptButton = this.button6; + resources.ApplyResources(this, "$this"); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.button1; + this.Controls.Add(this.button1); + this.Controls.Add(this.button6); + this.Controls.Add(this.button5); + this.Controls.Add(this.button4); + this.Controls.Add(this.textBox5); + this.Controls.Add(this.label7); + this.Controls.Add(this.listBox1); + this.Controls.Add(this.numericUpDown2); + this.Controls.Add(this.label6); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "CreateMultiSigContractDialog"; + this.ShowInTaskbar = false; + ((System.ComponentModel.ISupportInitialize)(this.numericUpDown2)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Button button5; + private System.Windows.Forms.Button button4; + private System.Windows.Forms.TextBox textBox5; + private System.Windows.Forms.Label label7; + private System.Windows.Forms.ListBox listBox1; + private System.Windows.Forms.NumericUpDown numericUpDown2; + private System.Windows.Forms.Label label6; + private System.Windows.Forms.Button button6; + private System.Windows.Forms.Button button1; + } +} diff --git a/src/Neo.GUI/GUI/CreateMultiSigContractDialog.cs b/src/Neo.GUI/GUI/CreateMultiSigContractDialog.cs new file mode 100644 index 0000000000..5dd10f858f --- /dev/null +++ b/src/Neo.GUI/GUI/CreateMultiSigContractDialog.cs @@ -0,0 +1,72 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// CreateMultiSigContractDialog.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Cryptography.ECC; +using Neo.SmartContract; +using Neo.Wallets; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows.Forms; +using static Neo.Program; + +namespace Neo.GUI +{ + internal partial class CreateMultiSigContractDialog : Form + { + private ECPoint[] publicKeys; + + public CreateMultiSigContractDialog() + { + InitializeComponent(); + } + + public Contract GetContract() + { + publicKeys = listBox1.Items.OfType().Select(p => ECPoint.DecodePoint(p.HexToBytes(), ECCurve.Secp256r1)).ToArray(); + return Contract.CreateMultiSigContract((int)numericUpDown2.Value, publicKeys); + } + + public KeyPair GetKey() + { + HashSet hashSet = new HashSet(publicKeys); + return Service.CurrentWallet.GetAccounts().FirstOrDefault(p => p.HasKey && hashSet.Contains(p.GetKey().PublicKey))?.GetKey(); + } + + private void numericUpDown2_ValueChanged(object sender, EventArgs e) + { + button6.Enabled = numericUpDown2.Value > 0; + } + + private void listBox1_SelectedIndexChanged(object sender, EventArgs e) + { + button5.Enabled = listBox1.SelectedIndices.Count > 0; + } + + private void textBox5_TextChanged(object sender, EventArgs e) + { + button4.Enabled = textBox5.TextLength > 0; + } + + private void button4_Click(object sender, EventArgs e) + { + listBox1.Items.Add(textBox5.Text); + textBox5.Clear(); + numericUpDown2.Maximum = listBox1.Items.Count; + } + + private void button5_Click(object sender, EventArgs e) + { + listBox1.Items.RemoveAt(listBox1.SelectedIndex); + numericUpDown2.Maximum = listBox1.Items.Count; + } + } +} diff --git a/src/Neo.GUI/GUI/CreateMultiSigContractDialog.es-ES.resx b/src/Neo.GUI/GUI/CreateMultiSigContractDialog.es-ES.resx new file mode 100644 index 0000000000..c5eefc3279 --- /dev/null +++ b/src/Neo.GUI/GUI/CreateMultiSigContractDialog.es-ES.resx @@ -0,0 +1,179 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + 573, 246 + + + 542, 246 + + + 168, 246 + + + 368, 23 + + + 147, 17 + + + Lista de claves públicas: + + + 168, 41 + + + 430, 199 + + + 168, 12 + + + 442, 275 + + + Confirmar + + + 523, 275 + + + Cancelar + + + + NoControl + + + 29, 14 + + + 133, 17 + + + Nº mínimo de firmas: + + + 610, 310 + + + Contrato con múltiples firmas + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/CreateMultiSigContractDialog.resx b/src/Neo.GUI/GUI/CreateMultiSigContractDialog.resx new file mode 100644 index 0000000000..fcd5dfc317 --- /dev/null +++ b/src/Neo.GUI/GUI/CreateMultiSigContractDialog.resx @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + False + + + + 425, 199 + + + 551, 310 + + + 114, 12 + + + button5 + + + $this + + + + 3, 4, 3, 4 + + + False + + + 514, 246 + + + True + + + System.Windows.Forms.NumericUpDown, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 12 + + + cancel + + + 5 + + + 14 + + + 12, 14 + + + button6 + + + textBox5 + + + 7, 17 + + + True + + + Min. Sig. Num.: + + + $this + + + Bottom, Right + + + 3 + + + 13 + + + Bottom, Right + + + 464, 275 + + + 114, 41 + + + 75, 23 + + + 93, 17 + + + confirm + + + $this + + + $this + + + numericUpDown2 + + + 483, 246 + + + 2 + + + System.Windows.Forms.ListBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 7 + + + 114, 246 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 15 + + + CenterScreen + + + False + + + 363, 23 + + + label7 + + + Bottom, Right + + + 25, 23 + + + 197, 23 + + + $this + + + 1 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 383, 275 + + + Bottom, Right + + + listBox1 + + + 7 + + + 6 + + + $this + + + 4 + + + 微软雅黑, 9pt + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Bottom, Left, Right + + + button1 + + + False + + + 15, 41 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 11 + + + $this + + + 0 + + + 8 + + + CreateMultiSigContractDialog + + + $this + + + 25, 23 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + - + + + $this + + + button4 + + + 8 + + + True + + + 96, 17 + + + Multi-Signature + + + 10 + + + 75, 23 + + + 17 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 9 + + + label6 + + + Top, Bottom, Left, Right + + + Public Key List: + + + True + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/CreateMultiSigContractDialog.zh-Hans.resx b/src/Neo.GUI/GUI/CreateMultiSigContractDialog.zh-Hans.resx new file mode 100644 index 0000000000..acd731d2a1 --- /dev/null +++ b/src/Neo.GUI/GUI/CreateMultiSigContractDialog.zh-Hans.resx @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + 491, 263 + + + 460, 263 + + + 99, 263 + + + 355, 23 + + + 34, 41 + + + 59, 17 + + + 公钥列表: + + + 99, 41 + + + 417, 216 + + + 99, 12 + + + 120, 23 + + + 83, 17 + + + 最小签名数量: + + + 360, 292 + + + 确定 + + + 441, 292 + + + 取消 + + + 528, 327 + + + 多方签名 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/CreateWalletDialog.cs b/src/Neo.GUI/GUI/CreateWalletDialog.cs new file mode 100644 index 0000000000..770cd07cac --- /dev/null +++ b/src/Neo.GUI/GUI/CreateWalletDialog.cs @@ -0,0 +1,72 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// CreateWalletDialog.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Windows.Forms; + +namespace Neo.GUI +{ + internal partial class CreateWalletDialog : Form + { + public CreateWalletDialog() + { + InitializeComponent(); + } + + public string Password + { + get + { + return textBox2.Text; + } + set + { + textBox2.Text = value; + textBox3.Text = value; + } + } + + public string WalletPath + { + get + { + return textBox1.Text; + } + set + { + textBox1.Text = value; + } + } + + private void textBox_TextChanged(object sender, EventArgs e) + { + if (textBox1.TextLength == 0 || textBox2.TextLength == 0 || textBox3.TextLength == 0) + { + button2.Enabled = false; + return; + } + if (textBox2.Text != textBox3.Text) + { + button2.Enabled = false; + return; + } + button2.Enabled = true; + } + + private void button1_Click(object sender, EventArgs e) + { + if (saveFileDialog1.ShowDialog() == DialogResult.OK) + { + textBox1.Text = saveFileDialog1.FileName; + } + } + } +} diff --git a/src/Neo.GUI/GUI/CreateWalletDialog.designer.cs b/src/Neo.GUI/GUI/CreateWalletDialog.designer.cs new file mode 100644 index 0000000000..1ea5451ff1 --- /dev/null +++ b/src/Neo.GUI/GUI/CreateWalletDialog.designer.cs @@ -0,0 +1,143 @@ +// Copyright (C) 2016-2024 The Neo Project. +// +// The neo-gui is free software distributed under the MIT software +// license, see the accompanying file LICENSE in the main directory of +// the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.GUI +{ + partial class CreateWalletDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(CreateWalletDialog)); + this.label1 = new System.Windows.Forms.Label(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.button1 = new System.Windows.Forms.Button(); + this.label2 = new System.Windows.Forms.Label(); + this.textBox2 = new System.Windows.Forms.TextBox(); + this.label3 = new System.Windows.Forms.Label(); + this.textBox3 = new System.Windows.Forms.TextBox(); + this.button2 = new System.Windows.Forms.Button(); + this.saveFileDialog1 = new System.Windows.Forms.SaveFileDialog(); + this.SuspendLayout(); + // + // label1 + // + resources.ApplyResources(this.label1, "label1"); + this.label1.Name = "label1"; + // + // textBox1 + // + resources.ApplyResources(this.textBox1, "textBox1"); + this.textBox1.Name = "textBox1"; + this.textBox1.ReadOnly = true; + this.textBox1.TextChanged += new System.EventHandler(this.textBox_TextChanged); + // + // button1 + // + resources.ApplyResources(this.button1, "button1"); + this.button1.Name = "button1"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // label2 + // + resources.ApplyResources(this.label2, "label2"); + this.label2.Name = "label2"; + // + // textBox2 + // + resources.ApplyResources(this.textBox2, "textBox2"); + this.textBox2.Name = "textBox2"; + this.textBox2.UseSystemPasswordChar = true; + this.textBox2.TextChanged += new System.EventHandler(this.textBox_TextChanged); + // + // label3 + // + resources.ApplyResources(this.label3, "label3"); + this.label3.Name = "label3"; + // + // textBox3 + // + resources.ApplyResources(this.textBox3, "textBox3"); + this.textBox3.Name = "textBox3"; + this.textBox3.UseSystemPasswordChar = true; + this.textBox3.TextChanged += new System.EventHandler(this.textBox_TextChanged); + // + // button2 + // + resources.ApplyResources(this.button2, "button2"); + this.button2.DialogResult = System.Windows.Forms.DialogResult.OK; + this.button2.Name = "button2"; + this.button2.UseVisualStyleBackColor = true; + // + // saveFileDialog1 + // + this.saveFileDialog1.DefaultExt = "json"; + resources.ApplyResources(this.saveFileDialog1, "saveFileDialog1"); + // + // CreateWalletDialog + // + this.AcceptButton = this.button2; + resources.ApplyResources(this, "$this"); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.button2); + this.Controls.Add(this.textBox3); + this.Controls.Add(this.label3); + this.Controls.Add(this.textBox2); + this.Controls.Add(this.label2); + this.Controls.Add(this.button1); + this.Controls.Add(this.textBox1); + this.Controls.Add(this.label1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "CreateWalletDialog"; + this.ShowInTaskbar = false; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.TextBox textBox2; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.TextBox textBox3; + private System.Windows.Forms.Button button2; + private System.Windows.Forms.SaveFileDialog saveFileDialog1; + } +} diff --git a/src/Neo.GUI/GUI/CreateWalletDialog.es-ES.resx b/src/Neo.GUI/GUI/CreateWalletDialog.es-ES.resx new file mode 100644 index 0000000000..09d7d9f325 --- /dev/null +++ b/src/Neo.GUI/GUI/CreateWalletDialog.es-ES.resx @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + 9, 16 + + + 137, 17 + + + Fichero de monedero: + + + 152, 13 + + + 293, 23 + + + Buscar... + + + 69, 51 + + + 77, 17 + + + Contraseña: + + + 152, 48 + + + 25, 84 + + + 121, 17 + + + Repetir contraseña: + + + 152, 81 + + + Confirmar + + + Nuevo monedero + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/CreateWalletDialog.resx b/src/Neo.GUI/GUI/CreateWalletDialog.resx new file mode 100644 index 0000000000..bfe06e458d --- /dev/null +++ b/src/Neo.GUI/GUI/CreateWalletDialog.resx @@ -0,0 +1,363 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + confirm + + + Wallet File: + + + Wallet File|*.json + + + $this + + + 5 + + + + 150, 23 + + + browse + + + label1 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 3 + + + 87, 17 + + + $this + + + 7, 17 + + + 0 + + + label3 + + + + True + + + + Top, Right + + + 105, 48 + + + CenterScreen + + + $this + + + 12, 84 + + + 7 + + + $this + + + 75, 23 + + + 0 + + + 5 + + + 70, 17 + + + 340, 23 + + + 6 + + + 105, 13 + + + 9 + + + 6 + + + saveFileDialog1 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 451, 12 + + + 4 + + + 67, 17 + + + $this + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + textBox1 + + + 2 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 微软雅黑, 9pt + + + Re-Password: + + + 8 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + button1 + + + True + + + 451, 86 + + + True + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + label2 + + + 1 + + + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 1 + + + Top, Left, Right + + + Bottom, Right + + + 29, 16 + + + New Wallet + + + Password: + + + 7 + + + System.Windows.Forms.SaveFileDialog, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 32, 51 + + + button2 + + + textBox3 + + + $this + + + 150, 23 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 105, 81 + + + 538, 121 + + + 75, 23 + + + CreateWalletDialog + + + textBox2 + + + 2 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + True + + + 17, 17 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/CreateWalletDialog.zh-Hans.resx b/src/Neo.GUI/GUI/CreateWalletDialog.zh-Hans.resx new file mode 100644 index 0000000000..ae934ad543 --- /dev/null +++ b/src/Neo.GUI/GUI/CreateWalletDialog.zh-Hans.resx @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + 12, 15 + + + 83, 17 + + + 钱包文件位置: + + + 101, 12 + + + 289, 23 + + + 396, 12 + + + 浏览 + + + 60, 44 + + + 35, 17 + + + 密码: + + + 101, 41 + + + 36, 73 + + + 59, 17 + + + 重复密码: + + + 101, 70 + + + 396, 70 + + + 确定 + + + 钱包文件|*.json + + + 483, 105 + + + 新建钱包 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/DeployContractDialog.Designer.cs b/src/Neo.GUI/GUI/DeployContractDialog.Designer.cs new file mode 100644 index 0000000000..8d29fbc1d3 --- /dev/null +++ b/src/Neo.GUI/GUI/DeployContractDialog.Designer.cs @@ -0,0 +1,308 @@ +// Copyright (C) 2016-2024 The Neo Project. +// +// The neo-gui is free software distributed under the MIT software +// license, see the accompanying file LICENSE in the main directory of +// the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.GUI +{ + partial class DeployContractDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(DeployContractDialog)); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.textBox5 = new System.Windows.Forms.TextBox(); + this.label5 = new System.Windows.Forms.Label(); + this.textBox4 = new System.Windows.Forms.TextBox(); + this.label4 = new System.Windows.Forms.Label(); + this.textBox3 = new System.Windows.Forms.TextBox(); + this.label3 = new System.Windows.Forms.Label(); + this.textBox2 = new System.Windows.Forms.TextBox(); + this.label2 = new System.Windows.Forms.Label(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.label1 = new System.Windows.Forms.Label(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.textBox7 = new System.Windows.Forms.TextBox(); + this.label7 = new System.Windows.Forms.Label(); + this.textBox6 = new System.Windows.Forms.TextBox(); + this.label6 = new System.Windows.Forms.Label(); + this.groupBox3 = new System.Windows.Forms.GroupBox(); + this.textBox9 = new System.Windows.Forms.TextBox(); + this.button1 = new System.Windows.Forms.Button(); + this.checkBox1 = new System.Windows.Forms.CheckBox(); + this.textBox8 = new System.Windows.Forms.TextBox(); + this.button2 = new System.Windows.Forms.Button(); + this.button3 = new System.Windows.Forms.Button(); + this.openFileDialog1 = new System.Windows.Forms.OpenFileDialog(); + this.checkBox2 = new System.Windows.Forms.CheckBox(); + this.checkBox3 = new System.Windows.Forms.CheckBox(); + this.label8 = new System.Windows.Forms.Label(); + this.groupBox1.SuspendLayout(); + this.groupBox2.SuspendLayout(); + this.groupBox3.SuspendLayout(); + this.SuspendLayout(); + // + // groupBox1 + // + resources.ApplyResources(this.groupBox1, "groupBox1"); + this.groupBox1.Controls.Add(this.textBox5); + this.groupBox1.Controls.Add(this.label5); + this.groupBox1.Controls.Add(this.textBox4); + this.groupBox1.Controls.Add(this.label4); + this.groupBox1.Controls.Add(this.textBox3); + this.groupBox1.Controls.Add(this.label3); + this.groupBox1.Controls.Add(this.textBox2); + this.groupBox1.Controls.Add(this.label2); + this.groupBox1.Controls.Add(this.textBox1); + this.groupBox1.Controls.Add(this.label1); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.TabStop = false; + // + // textBox5 + // + resources.ApplyResources(this.textBox5, "textBox5"); + this.textBox5.AcceptsReturn = true; + this.textBox5.AcceptsTab = true; + this.textBox5.Name = "textBox5"; + this.textBox5.TextChanged += new System.EventHandler(this.textBox_TextChanged); + // + // label5 + // + resources.ApplyResources(this.label5, "label5"); + this.label5.Name = "label5"; + // + // textBox4 + // + resources.ApplyResources(this.textBox4, "textBox4"); + this.textBox4.Name = "textBox4"; + this.textBox4.TextChanged += new System.EventHandler(this.textBox_TextChanged); + // + // label4 + // + resources.ApplyResources(this.label4, "label4"); + this.label4.Name = "label4"; + // + // textBox3 + // + resources.ApplyResources(this.textBox3, "textBox3"); + this.textBox3.Name = "textBox3"; + this.textBox3.TextChanged += new System.EventHandler(this.textBox_TextChanged); + // + // label3 + // + resources.ApplyResources(this.label3, "label3"); + this.label3.Name = "label3"; + // + // textBox2 + // + resources.ApplyResources(this.textBox2, "textBox2"); + this.textBox2.Name = "textBox2"; + this.textBox2.TextChanged += new System.EventHandler(this.textBox_TextChanged); + // + // label2 + // + resources.ApplyResources(this.label2, "label2"); + this.label2.Name = "label2"; + // + // textBox1 + // + resources.ApplyResources(this.textBox1, "textBox1"); + this.textBox1.Name = "textBox1"; + this.textBox1.TextChanged += new System.EventHandler(this.textBox_TextChanged); + // + // label1 + // + resources.ApplyResources(this.label1, "label1"); + this.label1.Name = "label1"; + // + // groupBox2 + // + resources.ApplyResources(this.groupBox2, "groupBox2"); + this.groupBox2.Controls.Add(this.textBox7); + this.groupBox2.Controls.Add(this.label7); + this.groupBox2.Controls.Add(this.textBox6); + this.groupBox2.Controls.Add(this.label6); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.TabStop = false; + // + // textBox7 + // + resources.ApplyResources(this.textBox7, "textBox7"); + this.textBox7.Name = "textBox7"; + // + // label7 + // + resources.ApplyResources(this.label7, "label7"); + this.label7.Name = "label7"; + // + // textBox6 + // + resources.ApplyResources(this.textBox6, "textBox6"); + this.textBox6.Name = "textBox6"; + // + // label6 + // + resources.ApplyResources(this.label6, "label6"); + this.label6.Name = "label6"; + // + // groupBox3 + // + resources.ApplyResources(this.groupBox3, "groupBox3"); + this.groupBox3.Controls.Add(this.label8); + this.groupBox3.Controls.Add(this.checkBox2); + this.groupBox3.Controls.Add(this.checkBox3); + this.groupBox3.Controls.Add(this.textBox9); + this.groupBox3.Controls.Add(this.button1); + this.groupBox3.Controls.Add(this.checkBox1); + this.groupBox3.Controls.Add(this.textBox8); + this.groupBox3.Name = "groupBox3"; + this.groupBox3.TabStop = false; + // + // textBox9 + // + resources.ApplyResources(this.textBox9, "textBox9"); + this.textBox9.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.textBox9.Name = "textBox9"; + this.textBox9.ReadOnly = true; + // + // button1 + // + resources.ApplyResources(this.button1, "button1"); + this.button1.Name = "button1"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // checkBox1 + // + resources.ApplyResources(this.checkBox1, "checkBox1"); + this.checkBox1.Name = "checkBox1"; + this.checkBox1.UseVisualStyleBackColor = true; + // + // textBox8 + // + resources.ApplyResources(this.textBox8, "textBox8"); + this.textBox8.Name = "textBox8"; + this.textBox8.TextChanged += new System.EventHandler(this.textBox_TextChanged); + // + // button2 + // + resources.ApplyResources(this.button2, "button2"); + this.button2.DialogResult = System.Windows.Forms.DialogResult.OK; + this.button2.Name = "button2"; + this.button2.UseVisualStyleBackColor = true; + // + // button3 + // + resources.ApplyResources(this.button3, "button3"); + this.button3.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.button3.Name = "button3"; + this.button3.UseVisualStyleBackColor = true; + // + // openFileDialog1 + // + resources.ApplyResources(this.openFileDialog1, "openFileDialog1"); + this.openFileDialog1.DefaultExt = "avm"; + // + // checkBox2 + // + resources.ApplyResources(this.checkBox2, "checkBox2"); + this.checkBox2.Name = "checkBox2"; + this.checkBox2.UseVisualStyleBackColor = true; + // + // checkBox3 + // + resources.ApplyResources(this.checkBox3, "checkBox3"); + this.checkBox3.Name = "checkBox3"; + this.checkBox3.UseVisualStyleBackColor = true; + // + // label8 + // + resources.ApplyResources(this.label8, "label8"); + this.label8.Name = "label8"; + // + // DeployContractDialog + // + resources.ApplyResources(this, "$this"); + this.AcceptButton = this.button2; + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.button3; + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.groupBox2); + this.Controls.Add(this.groupBox3); + this.Controls.Add(this.button3); + this.Controls.Add(this.button2); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "DeployContractDialog"; + this.ShowInTaskbar = false; + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.groupBox2.ResumeLayout(false); + this.groupBox2.PerformLayout(); + this.groupBox3.ResumeLayout(false); + this.groupBox3.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox textBox2; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.TextBox textBox3; + private System.Windows.Forms.TextBox textBox4; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.TextBox textBox5; + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.Label label6; + private System.Windows.Forms.TextBox textBox6; + private System.Windows.Forms.Label label7; + private System.Windows.Forms.TextBox textBox7; + private System.Windows.Forms.GroupBox groupBox3; + private System.Windows.Forms.TextBox textBox8; + private System.Windows.Forms.CheckBox checkBox1; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.Button button2; + private System.Windows.Forms.Button button3; + private System.Windows.Forms.OpenFileDialog openFileDialog1; + private System.Windows.Forms.TextBox textBox9; + private System.Windows.Forms.CheckBox checkBox2; + private System.Windows.Forms.Label label8; + private System.Windows.Forms.CheckBox checkBox3; + } +} diff --git a/src/Neo.GUI/GUI/DeployContractDialog.cs b/src/Neo.GUI/GUI/DeployContractDialog.cs new file mode 100644 index 0000000000..31ca5e6636 --- /dev/null +++ b/src/Neo.GUI/GUI/DeployContractDialog.cs @@ -0,0 +1,61 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// DeployContractDialog.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using System; +using System.IO; +using System.Windows.Forms; + +namespace Neo.GUI +{ + internal partial class DeployContractDialog : Form + { + public DeployContractDialog() + { + InitializeComponent(); + } + + public byte[] GetScript() + { + byte[] script = textBox8.Text.HexToBytes(); + string manifest = ""; + using ScriptBuilder sb = new ScriptBuilder(); + sb.EmitDynamicCall(NativeContract.ContractManagement.Hash, "deploy", script, manifest); + return sb.ToArray(); + } + + private void textBox_TextChanged(object sender, EventArgs e) + { + button2.Enabled = textBox1.TextLength > 0 + && textBox2.TextLength > 0 + && textBox3.TextLength > 0 + && textBox4.TextLength > 0 + && textBox5.TextLength > 0 + && textBox8.TextLength > 0; + try + { + textBox9.Text = textBox8.Text.HexToBytes().ToScriptHash().ToString(); + } + catch (FormatException) + { + textBox9.Text = ""; + } + } + + private void button1_Click(object sender, EventArgs e) + { + if (openFileDialog1.ShowDialog() != DialogResult.OK) return; + textBox8.Text = File.ReadAllBytes(openFileDialog1.FileName).ToHexString(); + } + } +} diff --git a/src/Neo.GUI/GUI/DeployContractDialog.es-ES.resx b/src/Neo.GUI/GUI/DeployContractDialog.es-ES.resx new file mode 100644 index 0000000000..7bd4a2e05b --- /dev/null +++ b/src/Neo.GUI/GUI/DeployContractDialog.es-ES.resx @@ -0,0 +1,217 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + 3, 141 + + + 79, 17 + + + Descripción: + + + 31, 112 + + + 52, 17 + + + Correo: + + + 40, 83 + + + 43, 17 + + + Autor: + + + Versión: + + + 23, 25 + + + 60, 17 + + + Nombre: + + + 140, 51 + + + 374, 23 + + + 43, 54 + + + 91, 17 + + + Tipo devuelto: + + + 140, 22 + + + 374, 23 + + + 128, 17 + + + Lista de parámetros: + + + Metadatos + + + Cargar + + + 199, 21 + + + Es necesario almacenamiento + + + Código + + + 368, 530 + + + 83, 23 + + + Desplegar + + + Cancelar + + + Desplegar contrato + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/DeployContractDialog.resx b/src/Neo.GUI/GUI/DeployContractDialog.resx new file mode 100644 index 0000000000..16bd3de8c2 --- /dev/null +++ b/src/Neo.GUI/GUI/DeployContractDialog.resx @@ -0,0 +1,972 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Top, Left, Right + + + Top, Bottom, Left, Right + + + + 114, 163 + + + 4, 4, 4, 4 + + + + True + + + Vertical + + + 545, 99 + + + 9 + + + textBox5 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox1 + + + 0 + + + True + + + NoControl + + + 8, 165 + + + 4, 0, 4, 0 + + + 97, 20 + + + 8 + + + Description: + + + label5 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox1 + + + 1 + + + Top, Left, Right + + + 114, 128 + + + 4, 4, 4, 4 + + + 545, 27 + + + 7 + + + textBox4 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox1 + + + 2 + + + True + + + NoControl + + + 53, 132 + + + 4, 0, 4, 0 + + + 51, 20 + + + 6 + + + Email: + + + label4 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox1 + + + 3 + + + Top, Left, Right + + + 114, 95 + + + 4, 4, 4, 4 + + + 545, 27 + + + 5 + + + textBox3 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox1 + + + 4 + + + True + + + NoControl + + + 42, 97 + + + 4, 0, 4, 0 + + + 64, 20 + + + 4 + + + Author: + + + label3 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox1 + + + 5 + + + Top, Left, Right + + + 114, 60 + + + 4, 4, 4, 4 + + + 545, 27 + + + 3 + + + textBox2 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox1 + + + 6 + + + True + + + NoControl + + + 36, 64 + + + 4, 0, 4, 0 + + + 68, 20 + + + 2 + + + Version: + + + label2 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox1 + + + 7 + + + Top, Left, Right + + + 114, 25 + + + 4, 4, 4, 4 + + + 545, 27 + + + 1 + + + textBox1 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox1 + + + 8 + + + True + + + 42, 29 + + + 4, 0, 4, 0 + + + 56, 20 + + + 0 + + + Name: + + + label1 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox1 + + + 9 + + + 15, 15 + + + 4, 4, 4, 4 + + + 4, 4, 4, 4 + + + 669, 268 + + + 0 + + + Information + + + groupBox1 + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 0 + + + 15, 289 + + + 4, 4, 4, 4 + + + 4, 4, 4, 4 + + + 669, 97 + + + 1 + + + Metadata + + + groupBox2 + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 1 + + + Top, Left, Right + + + 136, 60 + + + 4, 4, 4, 4 + + + 523, 27 + + + 3 + + + textBox7 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox2 + + + 0 + + + True + + + NoControl + + + 24, 64 + + + 4, 0, 4, 0 + + + 102, 20 + + + 2 + + + Return Type: + + + label7 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox2 + + + 1 + + + Top, Left, Right + + + 136, 25 + + + 4, 4, 4, 4 + + + 523, 27 + + + 1 + + + textBox6 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox2 + + + 2 + + + True + + + 8, 29 + + + 4, 0, 4, 0 + + + 117, 20 + + + 0 + + + Parameter List: + + + label6 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox2 + + + 3 + + + Top, Bottom, Left, Right + + + Bottom, Left + + + True + + + NoControl + + + 357, 189 + + + 4, 4, 4, 4 + + + 87, 24 + + + 3 + + + Payable + + + checkBox3 + + + System.Windows.Forms.CheckBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox3 + + + 0 + + + True + + + NoControl + + + 9, 162 + + + 4, 0, 4, 0 + + + 96, 20 + + + 3 + + + Script Hash: + + + label8 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox3 + + + 1 + + + Bottom, Left + + + True + + + NoControl + + + 180, 189 + + + 4, 4, 4, 4 + + + 127, 24 + + + 2 + + + Need Dyncall + + + checkBox2 + + + System.Windows.Forms.CheckBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox3 + + + 2 + + + Bottom, Left + + + 112, 162 + + + 4, 4, 4, 4 + + + 401, 20 + + + 4 + + + textBox9 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox3 + + + 3 + + + Bottom, Right + + + 564, 188 + + + 4, 4, 4, 4 + + + 96, 27 + + + 4 + + + Load + + + button1 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox3 + + + 4 + + + Bottom, Left + + + True + + + 8, 189 + + + 4, 4, 4, 4 + + + 133, 24 + + + 1 + + + Need Storage + + + checkBox1 + + + System.Windows.Forms.CheckBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox3 + + + 5 + + + Top, Bottom, Left, Right + + + 8, 25 + + + 4, 4, 4, 4 + + + True + + + Vertical + + + 652, 155 + + + 0 + + + textBox8 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox3 + + + 6 + + + 15, 395 + + + 4, 4, 4, 4 + + + 4, 4, 4, 4 + + + 669, 223 + + + 2 + + + Code + + + groupBox3 + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 2 + + + Bottom, Right + + + False + + + 483, 624 + + + 4, 4, 4, 4 + + + 96, 27 + + + 3 + + + Deploy + + + button2 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 4 + + + Bottom, Right + + + NoControl + + + 588, 624 + + + 4, 4, 4, 4 + + + 96, 27 + + + 4 + + + Cancel + + + button3 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 3 + + + 17, 17 + + + AVM File|*.avm + + + True + + + 9, 20 + + + 699, 665 + + + Microsoft YaHei, 9pt + + + 4, 5, 4, 5 + + + CenterScreen + + + Deploy Contract + + + openFileDialog1 + + + System.Windows.Forms.OpenFileDialog, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + DeployContractDialog + + + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + diff --git a/src/Neo.GUI/GUI/DeployContractDialog.zh-Hans.resx b/src/Neo.GUI/GUI/DeployContractDialog.zh-Hans.resx new file mode 100644 index 0000000000..ae91b44198 --- /dev/null +++ b/src/Neo.GUI/GUI/DeployContractDialog.zh-Hans.resx @@ -0,0 +1,259 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + 70, 122 + + + 444, 75 + + + 30, 125 + + + 34, 15 + + + 说明: + + + 70, 96 + + + 444, 23 + + + 6, 99 + + + 58, 15 + + + 电子邮件: + + + 70, 71 + + + 444, 23 + + + 30, 74 + + + 34, 15 + + + 作者: + + + 70, 45 + + + 444, 23 + + + 30, 48 + + + 34, 15 + + + 版本: + + + 70, 19 + + + 444, 23 + + + 30, 22 + + + 34, 15 + + + 名称: + + + 信息 + + + 70, 45 + + + 444, 23 + + + 18, 48 + + + 46, 15 + + + 返回值: + + + 70, 19 + + + 444, 23 + + + 58, 15 + + + 参数列表: + + + 元数据 + + + 98, 19 + + + 需要动态调用 + + + 加载 + + + 110, 19 + + + 需要创建存储区 + + + 代码 + + + 部署 + + + 取消 + + + AVM文件|*.avm + + + 部署合约 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/DeveloperToolsForm.ContractParameters.cs b/src/Neo.GUI/GUI/DeveloperToolsForm.ContractParameters.cs new file mode 100644 index 0000000000..1912fe9152 --- /dev/null +++ b/src/Neo.GUI/GUI/DeveloperToolsForm.ContractParameters.cs @@ -0,0 +1,118 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// DeveloperToolsForm.ContractParameters.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Akka.Actor; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Properties; +using Neo.SmartContract; +using Neo.Wallets; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows.Forms; +using static Neo.Program; + +namespace Neo.GUI +{ + partial class DeveloperToolsForm + { + private ContractParametersContext context; + + private void listBox1_SelectedIndexChanged(object sender, EventArgs e) + { + if (listBox1.SelectedIndex < 0) return; + listBox2.Items.Clear(); + if (Service.CurrentWallet == null) return; + UInt160 hash = ((string)listBox1.SelectedItem).ToScriptHash(Service.NeoSystem.Settings.AddressVersion); + var parameters = context.GetParameters(hash); + if (parameters == null) + { + var parameterList = Service.CurrentWallet.GetAccount(hash).Contract.ParameterList; + if (parameterList != null) + { + var pList = new List(); + for (int i = 0; i < parameterList.Length; i++) + { + pList.Add(new ContractParameter(parameterList[i])); + context.Add(Service.CurrentWallet.GetAccount(hash).Contract, i, null); + } + } + } + listBox2.Items.AddRange(context.GetParameters(hash).ToArray()); + button4.Visible = context.Completed; + } + + private void listBox2_SelectedIndexChanged(object sender, EventArgs e) + { + if (listBox2.SelectedIndex < 0) return; + textBox1.Text = listBox2.SelectedItem.ToString(); + textBox2.Clear(); + } + + private void button1_Click(object sender, EventArgs e) + { + string input = InputBox.Show("ParametersContext", "ParametersContext"); + if (string.IsNullOrEmpty(input)) return; + try + { + context = ContractParametersContext.Parse(input, Service.NeoSystem.StoreView); + } + catch (FormatException ex) + { + MessageBox.Show(ex.Message); + return; + } + listBox1.Items.Clear(); + listBox2.Items.Clear(); + textBox1.Clear(); + textBox2.Clear(); + listBox1.Items.AddRange(context.ScriptHashes.Select(p => p.ToAddress(Service.NeoSystem.Settings.AddressVersion)).ToArray()); + button2.Enabled = true; + button4.Visible = context.Completed; + } + + private void button2_Click(object sender, EventArgs e) + { + InformationBox.Show(context.ToString(), "ParametersContext", "ParametersContext"); + } + + private void button3_Click(object sender, EventArgs e) + { + if (listBox1.SelectedIndex < 0) return; + if (listBox2.SelectedIndex < 0) return; + ContractParameter parameter = (ContractParameter)listBox2.SelectedItem; + parameter.SetValue(textBox2.Text); + listBox2.Items[listBox2.SelectedIndex] = parameter; + textBox1.Text = textBox2.Text; + button4.Visible = context.Completed; + } + + private void button4_Click(object sender, EventArgs e) + { + if (!(context.Verifiable is Transaction tx)) + { + MessageBox.Show("Only support to broadcast transaction."); + return; + } + tx.Witnesses = context.GetWitnesses(); + Blockchain.RelayResult reason = Service.NeoSystem.Blockchain.Ask(tx).Result; + if (reason.Result == VerifyResult.Succeed) + { + InformationBox.Show(tx.Hash.ToString(), Strings.RelaySuccessText, Strings.RelaySuccessTitle); + } + else + { + MessageBox.Show($"Transaction cannot be broadcast: {reason}"); + } + } + } +} diff --git a/src/Neo.GUI/GUI/DeveloperToolsForm.Designer.cs b/src/Neo.GUI/GUI/DeveloperToolsForm.Designer.cs new file mode 100644 index 0000000000..a18dcdb108 --- /dev/null +++ b/src/Neo.GUI/GUI/DeveloperToolsForm.Designer.cs @@ -0,0 +1,259 @@ +// Copyright (C) 2016-2024 The Neo Project. +// +// The neo-gui is free software distributed under the MIT software +// license, see the accompanying file LICENSE in the main directory of +// the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.GUI +{ + partial class DeveloperToolsForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(DeveloperToolsForm)); + this.splitContainer1 = new System.Windows.Forms.SplitContainer(); + this.propertyGrid1 = new System.Windows.Forms.PropertyGrid(); + this.button8 = new System.Windows.Forms.Button(); + this.tabControl1 = new System.Windows.Forms.TabControl(); + this.tabPage1 = new System.Windows.Forms.TabPage(); + this.tabPage2 = new System.Windows.Forms.TabPage(); + this.button4 = new System.Windows.Forms.Button(); + this.button3 = new System.Windows.Forms.Button(); + this.button2 = new System.Windows.Forms.Button(); + this.button1 = new System.Windows.Forms.Button(); + this.groupBox4 = new System.Windows.Forms.GroupBox(); + this.textBox2 = new System.Windows.Forms.TextBox(); + this.groupBox3 = new System.Windows.Forms.GroupBox(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.listBox2 = new System.Windows.Forms.ListBox(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.listBox1 = new System.Windows.Forms.ListBox(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); + this.splitContainer1.Panel1.SuspendLayout(); + this.splitContainer1.Panel2.SuspendLayout(); + this.splitContainer1.SuspendLayout(); + this.tabControl1.SuspendLayout(); + this.tabPage1.SuspendLayout(); + this.tabPage2.SuspendLayout(); + this.groupBox4.SuspendLayout(); + this.groupBox3.SuspendLayout(); + this.groupBox2.SuspendLayout(); + this.groupBox1.SuspendLayout(); + this.SuspendLayout(); + // + // splitContainer1 + // + resources.ApplyResources(this.splitContainer1, "splitContainer1"); + this.splitContainer1.FixedPanel = System.Windows.Forms.FixedPanel.Panel2; + this.splitContainer1.Name = "splitContainer1"; + // + // splitContainer1.Panel1 + // + resources.ApplyResources(this.splitContainer1.Panel1, "splitContainer1.Panel1"); + this.splitContainer1.Panel1.Controls.Add(this.propertyGrid1); + // + // splitContainer1.Panel2 + // + resources.ApplyResources(this.splitContainer1.Panel2, "splitContainer1.Panel2"); + this.splitContainer1.Panel2.Controls.Add(this.button8); + // + // propertyGrid1 + // + resources.ApplyResources(this.propertyGrid1, "propertyGrid1"); + this.propertyGrid1.Name = "propertyGrid1"; + this.propertyGrid1.SelectedObjectsChanged += new System.EventHandler(this.propertyGrid1_SelectedObjectsChanged); + // + // button8 + // + resources.ApplyResources(this.button8, "button8"); + this.button8.Name = "button8"; + this.button8.UseVisualStyleBackColor = true; + this.button8.Click += new System.EventHandler(this.button8_Click); + // + // tabControl1 + // + resources.ApplyResources(this.tabControl1, "tabControl1"); + this.tabControl1.Controls.Add(this.tabPage1); + this.tabControl1.Controls.Add(this.tabPage2); + this.tabControl1.Name = "tabControl1"; + this.tabControl1.SelectedIndex = 0; + // + // tabPage1 + // + resources.ApplyResources(this.tabPage1, "tabPage1"); + this.tabPage1.Controls.Add(this.splitContainer1); + this.tabPage1.Name = "tabPage1"; + this.tabPage1.UseVisualStyleBackColor = true; + // + // tabPage2 + // + resources.ApplyResources(this.tabPage2, "tabPage2"); + this.tabPage2.Controls.Add(this.button4); + this.tabPage2.Controls.Add(this.button3); + this.tabPage2.Controls.Add(this.button2); + this.tabPage2.Controls.Add(this.button1); + this.tabPage2.Controls.Add(this.groupBox4); + this.tabPage2.Controls.Add(this.groupBox3); + this.tabPage2.Controls.Add(this.groupBox2); + this.tabPage2.Controls.Add(this.groupBox1); + this.tabPage2.Name = "tabPage2"; + this.tabPage2.UseVisualStyleBackColor = true; + // + // button4 + // + resources.ApplyResources(this.button4, "button4"); + this.button4.Name = "button4"; + this.button4.UseVisualStyleBackColor = true; + this.button4.Click += new System.EventHandler(this.button4_Click); + // + // button3 + // + resources.ApplyResources(this.button3, "button3"); + this.button3.Name = "button3"; + this.button3.UseVisualStyleBackColor = true; + this.button3.Click += new System.EventHandler(this.button3_Click); + // + // button2 + // + resources.ApplyResources(this.button2, "button2"); + this.button2.Name = "button2"; + this.button2.UseVisualStyleBackColor = true; + this.button2.Click += new System.EventHandler(this.button2_Click); + // + // button1 + // + resources.ApplyResources(this.button1, "button1"); + this.button1.Name = "button1"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // groupBox4 + // + resources.ApplyResources(this.groupBox4, "groupBox4"); + this.groupBox4.Controls.Add(this.textBox2); + this.groupBox4.Name = "groupBox4"; + this.groupBox4.TabStop = false; + // + // textBox2 + // + resources.ApplyResources(this.textBox2, "textBox2"); + this.textBox2.Name = "textBox2"; + // + // groupBox3 + // + resources.ApplyResources(this.groupBox3, "groupBox3"); + this.groupBox3.Controls.Add(this.textBox1); + this.groupBox3.Name = "groupBox3"; + this.groupBox3.TabStop = false; + // + // textBox1 + // + resources.ApplyResources(this.textBox1, "textBox1"); + this.textBox1.Name = "textBox1"; + this.textBox1.ReadOnly = true; + // + // groupBox2 + // + resources.ApplyResources(this.groupBox2, "groupBox2"); + this.groupBox2.Controls.Add(this.listBox2); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.TabStop = false; + // + // listBox2 + // + resources.ApplyResources(this.listBox2, "listBox2"); + this.listBox2.FormattingEnabled = true; + this.listBox2.Name = "listBox2"; + this.listBox2.SelectedIndexChanged += new System.EventHandler(this.listBox2_SelectedIndexChanged); + // + // groupBox1 + // + resources.ApplyResources(this.groupBox1, "groupBox1"); + this.groupBox1.Controls.Add(this.listBox1); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.TabStop = false; + // + // listBox1 + // + resources.ApplyResources(this.listBox1, "listBox1"); + this.listBox1.FormattingEnabled = true; + this.listBox1.Name = "listBox1"; + this.listBox1.SelectedIndexChanged += new System.EventHandler(this.listBox1_SelectedIndexChanged); + // + // DeveloperToolsForm + // + resources.ApplyResources(this, "$this"); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.tabControl1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.KeyPreview = true; + this.MaximizeBox = false; + this.Name = "DeveloperToolsForm"; + this.splitContainer1.Panel1.ResumeLayout(false); + this.splitContainer1.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); + this.splitContainer1.ResumeLayout(false); + this.tabControl1.ResumeLayout(false); + this.tabPage1.ResumeLayout(false); + this.tabPage2.ResumeLayout(false); + this.groupBox4.ResumeLayout(false); + this.groupBox4.PerformLayout(); + this.groupBox3.ResumeLayout(false); + this.groupBox3.PerformLayout(); + this.groupBox2.ResumeLayout(false); + this.groupBox1.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.TabControl tabControl1; + private System.Windows.Forms.TabPage tabPage2; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.GroupBox groupBox3; + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.ListBox listBox1; + private System.Windows.Forms.ListBox listBox2; + private System.Windows.Forms.GroupBox groupBox4; + private System.Windows.Forms.TextBox textBox2; + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.Button button3; + private System.Windows.Forms.Button button2; + private System.Windows.Forms.Button button4; + private System.Windows.Forms.TabPage tabPage1; + private System.Windows.Forms.SplitContainer splitContainer1; + private System.Windows.Forms.PropertyGrid propertyGrid1; + private System.Windows.Forms.Button button8; + } +} diff --git a/src/Neo.GUI/GUI/DeveloperToolsForm.TxBuilder.cs b/src/Neo.GUI/GUI/DeveloperToolsForm.TxBuilder.cs new file mode 100644 index 0000000000..b8844fd85d --- /dev/null +++ b/src/Neo.GUI/GUI/DeveloperToolsForm.TxBuilder.cs @@ -0,0 +1,37 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// DeveloperToolsForm.TxBuilder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.GUI.Wrappers; +using Neo.SmartContract; +using System; + +namespace Neo.GUI +{ + partial class DeveloperToolsForm + { + private void InitializeTxBuilder() + { + propertyGrid1.SelectedObject = new TransactionWrapper(); + } + + private void propertyGrid1_SelectedObjectsChanged(object sender, EventArgs e) + { + splitContainer1.Panel2.Enabled = propertyGrid1.SelectedObject != null; + } + + private void button8_Click(object sender, EventArgs e) + { + TransactionWrapper wrapper = (TransactionWrapper)propertyGrid1.SelectedObject; + ContractParametersContext context = new ContractParametersContext(Program.Service.NeoSystem.StoreView, wrapper.Unwrap(), Program.Service.NeoSystem.Settings.Network); + InformationBox.Show(context.ToString(), "ParametersContext", "ParametersContext"); + } + } +} diff --git a/src/Neo.GUI/GUI/DeveloperToolsForm.cs b/src/Neo.GUI/GUI/DeveloperToolsForm.cs new file mode 100644 index 0000000000..3c5d6ec952 --- /dev/null +++ b/src/Neo.GUI/GUI/DeveloperToolsForm.cs @@ -0,0 +1,24 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// DeveloperToolsForm.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Windows.Forms; + +namespace Neo.GUI +{ + internal partial class DeveloperToolsForm : Form + { + public DeveloperToolsForm() + { + InitializeComponent(); + InitializeTxBuilder(); + } + } +} diff --git a/src/Neo.GUI/GUI/DeveloperToolsForm.es-ES.resx b/src/Neo.GUI/GUI/DeveloperToolsForm.es-ES.resx new file mode 100644 index 0000000000..9e330876eb --- /dev/null +++ b/src/Neo.GUI/GUI/DeveloperToolsForm.es-ES.resx @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Parametros de contexto + + + Parámetros del contrato + + + Emitir + + + Actualizar + + + Mostrar + + + Cargar + + + Nuevo valor + + + Valor actual + + + Parámetros + + + Hash del script + + + Herramienta de desarrollo + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/DeveloperToolsForm.resx b/src/Neo.GUI/GUI/DeveloperToolsForm.resx new file mode 100644 index 0000000000..63e49aa4b5 --- /dev/null +++ b/src/Neo.GUI/GUI/DeveloperToolsForm.resx @@ -0,0 +1,669 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Fill + + + + 3, 3 + + + Fill + + + 0, 0 + + + 444, 414 + + + + 1 + + + propertyGrid1 + + + System.Windows.Forms.PropertyGrid, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + splitContainer1.Panel1 + + + 0 + + + splitContainer1.Panel1 + + + System.Windows.Forms.SplitterPanel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + splitContainer1 + + + 0 + + + Bottom, Left, Right + + + NoControl + + + 3, 386 + + + 173, 23 + + + 3 + + + Get Parameters Context + + + button8 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + splitContainer1.Panel2 + + + 0 + + + False + + + splitContainer1.Panel2 + + + System.Windows.Forms.SplitterPanel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + splitContainer1 + + + 1 + + + 627, 414 + + + 444 + + + 1 + + + splitContainer1 + + + System.Windows.Forms.SplitContainer, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tabPage1 + + + 0 + + + 4, 26 + + + 3, 3, 3, 3 + + + 633, 420 + + + 3 + + + Tx Builder + + + tabPage1 + + + System.Windows.Forms.TabPage, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tabControl1 + + + 0 + + + Bottom, Left + + + 170, 389 + + + 75, 23 + + + 7 + + + Broadcast + + + False + + + button4 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tabPage2 + + + 0 + + + Bottom, Right + + + 550, 389 + + + 75, 23 + + + 6 + + + Update + + + button3 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tabPage2 + + + 1 + + + Bottom, Left + + + False + + + 89, 389 + + + 75, 23 + + + 5 + + + Show + + + button2 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tabPage2 + + + 2 + + + Bottom, Left + + + 8, 389 + + + 75, 23 + + + 4 + + + Load + + + button1 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tabPage2 + + + 3 + + + Bottom, Left, Right + + + Fill + + + 3, 19 + + + True + + + 199, 98 + + + 0 + + + textBox2 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox4 + + + 0 + + + 420, 263 + + + 205, 120 + + + 3 + + + New Value + + + groupBox4 + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tabPage2 + + + 4 + + + Top, Bottom, Left, Right + + + Fill + + + 3, 19 + + + True + + + 199, 229 + + + 0 + + + textBox1 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox3 + + + 0 + + + 420, 6 + + + 205, 251 + + + 2 + + + Current Value + + + groupBox3 + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tabPage2 + + + 5 + + + Top, Bottom, Left + + + Fill + + + False + + + 17 + + + 3, 19 + + + 194, 355 + + + 0 + + + listBox2 + + + System.Windows.Forms.ListBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox2 + + + 0 + + + 214, 6 + + + 200, 377 + + + 1 + + + Parameters + + + groupBox2 + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tabPage2 + + + 6 + + + Top, Bottom, Left + + + Fill + + + False + + + 17 + + + 3, 19 + + + 194, 355 + + + 0 + + + listBox1 + + + System.Windows.Forms.ListBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox1 + + + 0 + + + 8, 6 + + + 200, 377 + + + 0 + + + ScriptHash + + + groupBox1 + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tabPage2 + + + 7 + + + 4, 26 + + + 3, 3, 3, 3 + + + 633, 420 + + + 2 + + + Contract Parameters + + + tabPage2 + + + System.Windows.Forms.TabPage, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tabControl1 + + + 1 + + + Fill + + + 0, 0 + + + 641, 450 + + + 0 + + + tabControl1 + + + System.Windows.Forms.TabControl, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 0 + + + True + + + 7, 17 + + + 641, 450 + + + 微软雅黑, 9pt + + + CenterScreen + + + Neo Developer Tools + + + DeveloperToolsForm + + + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/DeveloperToolsForm.zh-Hans.resx b/src/Neo.GUI/GUI/DeveloperToolsForm.zh-Hans.resx new file mode 100644 index 0000000000..2b25fc62cb --- /dev/null +++ b/src/Neo.GUI/GUI/DeveloperToolsForm.zh-Hans.resx @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 获取合约参数上下文 + + + 交易构造器 + + + 广播 + + + 更新 + + + 显示 + + + 加载 + + + 新值 + + + 当前值 + + + 参数 + + + 合约参数 + + + NEO开发人员工具 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/ElectionDialog.Designer.cs b/src/Neo.GUI/GUI/ElectionDialog.Designer.cs new file mode 100644 index 0000000000..5085e1db1f --- /dev/null +++ b/src/Neo.GUI/GUI/ElectionDialog.Designer.cs @@ -0,0 +1,92 @@ +// Copyright (C) 2016-2024 The Neo Project. +// +// The neo-gui is free software distributed under the MIT software +// license, see the accompanying file LICENSE in the main directory of +// the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.GUI +{ + partial class ElectionDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ElectionDialog)); + this.label1 = new System.Windows.Forms.Label(); + this.comboBox1 = new System.Windows.Forms.ComboBox(); + this.button1 = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // label1 + // + resources.ApplyResources(this.label1, "label1"); + this.label1.Name = "label1"; + // + // comboBox1 + // + resources.ApplyResources(this.comboBox1, "comboBox1"); + this.comboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboBox1.FormattingEnabled = true; + this.comboBox1.Name = "comboBox1"; + this.comboBox1.SelectedIndexChanged += new System.EventHandler(this.comboBox1_SelectedIndexChanged); + // + // button1 + // + resources.ApplyResources(this.button1, "button1"); + this.button1.DialogResult = System.Windows.Forms.DialogResult.OK; + this.button1.Name = "button1"; + this.button1.UseVisualStyleBackColor = true; + // + // ElectionDialog + // + this.AcceptButton = this.button1; + resources.ApplyResources(this, "$this"); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.button1); + this.Controls.Add(this.comboBox1); + this.Controls.Add(this.label1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "ElectionDialog"; + this.ShowInTaskbar = false; + this.Load += new System.EventHandler(this.ElectionDialog_Load); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label label1; + private System.Windows.Forms.ComboBox comboBox1; + private System.Windows.Forms.Button button1; + } +} diff --git a/src/Neo.GUI/GUI/ElectionDialog.cs b/src/Neo.GUI/GUI/ElectionDialog.cs new file mode 100644 index 0000000000..ddc730589e --- /dev/null +++ b/src/Neo.GUI/GUI/ElectionDialog.cs @@ -0,0 +1,52 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ElectionDialog.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Cryptography.ECC; +using Neo.IO; +using Neo.SmartContract.Native; +using Neo.VM; +using System; +using System.Linq; +using System.Windows.Forms; +using static Neo.Program; +using static Neo.SmartContract.Helper; + +namespace Neo.GUI +{ + public partial class ElectionDialog : Form + { + public ElectionDialog() + { + InitializeComponent(); + } + + public byte[] GetScript() + { + ECPoint pubkey = (ECPoint)comboBox1.SelectedItem; + using ScriptBuilder sb = new ScriptBuilder(); + sb.EmitDynamicCall(NativeContract.NEO.Hash, "registerValidator", pubkey); + return sb.ToArray(); + } + + private void ElectionDialog_Load(object sender, EventArgs e) + { + comboBox1.Items.AddRange(Service.CurrentWallet.GetAccounts().Where(p => !p.WatchOnly && IsSignatureContract(p.Contract.Script)).Select(p => p.GetKey().PublicKey).ToArray()); + } + + private void comboBox1_SelectedIndexChanged(object sender, EventArgs e) + { + if (comboBox1.SelectedIndex >= 0) + { + button1.Enabled = true; + } + } + } +} diff --git a/src/Neo.GUI/GUI/ElectionDialog.es-ES.resx b/src/Neo.GUI/GUI/ElectionDialog.es-ES.resx new file mode 100644 index 0000000000..5ab76de86d --- /dev/null +++ b/src/Neo.GUI/GUI/ElectionDialog.es-ES.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Votación + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/ElectionDialog.resx b/src/Neo.GUI/GUI/ElectionDialog.resx new file mode 100644 index 0000000000..ea655e7977 --- /dev/null +++ b/src/Neo.GUI/GUI/ElectionDialog.resx @@ -0,0 +1,231 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + True + + + + 12, 17 + + + 70, 17 + + + 0 + + + Public Key: + + + label1 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 2 + + + + Top, Left, Right + + + 83, 14 + + + 442, 25 + + + 9 + + + comboBox1 + + + System.Windows.Forms.ComboBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 1 + + + Bottom, Right + + + False + + + 450, 56 + + + 75, 26 + + + 12 + + + OK + + + button1 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 0 + + + True + + + 7, 17 + + + 537, 95 + + + 微软雅黑, 9pt + + + 3, 5, 3, 5 + + + CenterScreen + + + Election + + + ElectionDialog + + + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/ElectionDialog.zh-Hans.resx b/src/Neo.GUI/GUI/ElectionDialog.zh-Hans.resx new file mode 100644 index 0000000000..53e9edf8f2 --- /dev/null +++ b/src/Neo.GUI/GUI/ElectionDialog.zh-Hans.resx @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + 24, 15 + + + 44, 17 + + + 公钥: + + + 73, 12 + + + 452, 25 + + + 确定 + + + + NoControl + + + 选举 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/Helper.cs b/src/Neo.GUI/GUI/Helper.cs new file mode 100644 index 0000000000..fe06e78429 --- /dev/null +++ b/src/Neo.GUI/GUI/Helper.cs @@ -0,0 +1,74 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Helper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Akka.Actor; +using Neo.Network.P2P.Payloads; +using Neo.Properties; +using Neo.SmartContract; +using System; +using System.Collections.Generic; +using System.Windows.Forms; +using static Neo.Program; + +namespace Neo.GUI +{ + internal static class Helper + { + private static readonly Dictionary tool_forms = new Dictionary(); + + private static void Helper_FormClosing(object sender, FormClosingEventArgs e) + { + tool_forms.Remove(sender.GetType()); + } + + public static void Show() where T : Form, new() + { + Type t = typeof(T); + if (!tool_forms.ContainsKey(t)) + { + tool_forms.Add(t, new T()); + tool_forms[t].FormClosing += Helper_FormClosing; + } + tool_forms[t].Show(); + tool_forms[t].Activate(); + } + + public static void SignAndShowInformation(Transaction tx) + { + if (tx == null) + { + MessageBox.Show(Strings.InsufficientFunds); + return; + } + ContractParametersContext context; + try + { + context = new ContractParametersContext(Service.NeoSystem.StoreView, tx, Program.Service.NeoSystem.Settings.Network); + } + catch (InvalidOperationException) + { + MessageBox.Show(Strings.UnsynchronizedBlock); + return; + } + Service.CurrentWallet.Sign(context); + if (context.Completed) + { + tx.Witnesses = context.GetWitnesses(); + Service.NeoSystem.Blockchain.Tell(tx); + InformationBox.Show(tx.Hash.ToString(), Strings.SendTxSucceedMessage, Strings.SendTxSucceedTitle); + } + else + { + InformationBox.Show(context.ToString(), Strings.IncompletedSignatureMessage, Strings.IncompletedSignatureTitle); + } + } + } +} diff --git a/src/Neo.GUI/GUI/ImportCustomContractDialog.Designer.cs b/src/Neo.GUI/GUI/ImportCustomContractDialog.Designer.cs new file mode 100644 index 0000000000..43e1a0cad3 --- /dev/null +++ b/src/Neo.GUI/GUI/ImportCustomContractDialog.Designer.cs @@ -0,0 +1,137 @@ +// Copyright (C) 2016-2024 The Neo Project. +// +// The neo-gui is free software distributed under the MIT software +// license, see the accompanying file LICENSE in the main directory of +// the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.GUI +{ + partial class ImportCustomContractDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ImportCustomContractDialog)); + this.label1 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.textBox2 = new System.Windows.Forms.TextBox(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.button1 = new System.Windows.Forms.Button(); + this.button2 = new System.Windows.Forms.Button(); + this.textBox3 = new System.Windows.Forms.TextBox(); + this.groupBox1.SuspendLayout(); + this.SuspendLayout(); + // + // label1 + // + resources.ApplyResources(this.label1, "label1"); + this.label1.Name = "label1"; + // + // label2 + // + resources.ApplyResources(this.label2, "label2"); + this.label2.Name = "label2"; + // + // textBox1 + // + resources.ApplyResources(this.textBox1, "textBox1"); + this.textBox1.Name = "textBox1"; + this.textBox1.TextChanged += new System.EventHandler(this.Input_Changed); + // + // textBox2 + // + resources.ApplyResources(this.textBox2, "textBox2"); + this.textBox2.Name = "textBox2"; + this.textBox2.TextChanged += new System.EventHandler(this.Input_Changed); + // + // groupBox1 + // + resources.ApplyResources(this.groupBox1, "groupBox1"); + this.groupBox1.Controls.Add(this.textBox2); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.TabStop = false; + // + // button1 + // + resources.ApplyResources(this.button1, "button1"); + this.button1.DialogResult = System.Windows.Forms.DialogResult.OK; + this.button1.Name = "button1"; + this.button1.UseVisualStyleBackColor = true; + // + // button2 + // + resources.ApplyResources(this.button2, "button2"); + this.button2.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.button2.Name = "button2"; + this.button2.UseVisualStyleBackColor = true; + // + // textBox3 + // + resources.ApplyResources(this.textBox3, "textBox3"); + this.textBox3.Name = "textBox3"; + // + // ImportCustomContractDialog + // + this.AcceptButton = this.button1; + resources.ApplyResources(this, "$this"); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.button2; + this.Controls.Add(this.textBox3); + this.Controls.Add(this.button2); + this.Controls.Add(this.button1); + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.textBox1); + this.Controls.Add(this.label2); + this.Controls.Add(this.label1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "ImportCustomContractDialog"; + this.ShowInTaskbar = false; + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.TextBox textBox2; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.Button button2; + private System.Windows.Forms.TextBox textBox3; + } +} diff --git a/src/Neo.GUI/GUI/ImportCustomContractDialog.cs b/src/Neo.GUI/GUI/ImportCustomContractDialog.cs new file mode 100644 index 0000000000..b8df534699 --- /dev/null +++ b/src/Neo.GUI/GUI/ImportCustomContractDialog.cs @@ -0,0 +1,54 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ImportCustomContractDialog.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.SmartContract; +using Neo.Wallets; +using System; +using System.Linq; +using System.Windows.Forms; + +namespace Neo.GUI +{ + internal partial class ImportCustomContractDialog : Form + { + public Contract GetContract() + { + ContractParameterType[] parameterList = textBox1.Text.HexToBytes().Select(p => (ContractParameterType)p).ToArray(); + byte[] redeemScript = textBox2.Text.HexToBytes(); + return Contract.Create(parameterList, redeemScript); + } + + public KeyPair GetKey() + { + if (textBox3.TextLength == 0) return null; + byte[] privateKey; + try + { + privateKey = Wallet.GetPrivateKeyFromWIF(textBox3.Text); + } + catch (FormatException) + { + privateKey = textBox3.Text.HexToBytes(); + } + return new KeyPair(privateKey); + } + + public ImportCustomContractDialog() + { + InitializeComponent(); + } + + private void Input_Changed(object sender, EventArgs e) + { + button1.Enabled = textBox1.TextLength > 0 && textBox2.TextLength > 0; + } + } +} diff --git a/src/Neo.GUI/GUI/ImportCustomContractDialog.es-ES.resx b/src/Neo.GUI/GUI/ImportCustomContractDialog.es-ES.resx new file mode 100644 index 0000000000..a61aa86444 --- /dev/null +++ b/src/Neo.GUI/GUI/ImportCustomContractDialog.es-ES.resx @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + 13, 15 + + + 23, 44 + + + 114, 16 + + + Lista de parámetros: + + + 143, 41 + + + 433, 23 + + + Confirmar + + + Cancelar + + + 143, 12 + + + 433, 23 + + + Importar contrato personalizado + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/ImportCustomContractDialog.resx b/src/Neo.GUI/GUI/ImportCustomContractDialog.resx new file mode 100644 index 0000000000..f7b634476e --- /dev/null +++ b/src/Neo.GUI/GUI/ImportCustomContractDialog.resx @@ -0,0 +1,366 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + True + + + + 12, 15 + + + 124, 16 + + + 0 + + + Private Key (optional): + + + label1 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 6 + + + True + + + 50, 44 + + + 86, 16 + + + 10 + + + Parameter List: + + + label2 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 5 + + + + Top, Left, Right + + + 142, 41 + + + 434, 23 + + + 11 + + + textBox1 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 4 + + + Fill + + + 3, 19 + + + 131072 + + + True + + + Vertical + + + 558, 323 + + + 13 + + + textBox2 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox1 + + + 0 + + + Top, Bottom, Left, Right + + + 12, 70 + + + 564, 345 + + + 14 + + + Script + + + groupBox1 + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 3 + + + Bottom, Right + + + False + + + 420, 421 + + + 75, 23 + + + 15 + + + OK + + + button1 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 2 + + + Bottom, Right + + + 501, 421 + + + 75, 23 + + + 16 + + + Cancel + + + button2 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 1 + + + 142, 12 + + + 434, 23 + + + 17 + + + textBox3 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 0 + + + True + + + 7, 16 + + + 588, 456 + + + 微软雅黑, 9pt + + + 3, 4, 3, 4 + + + CenterScreen + + + Import Custom Contract + + + ImportCustomContractDialog + + + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/ImportCustomContractDialog.zh-Hans.resx b/src/Neo.GUI/GUI/ImportCustomContractDialog.zh-Hans.resx new file mode 100644 index 0000000000..78fbe3ff92 --- /dev/null +++ b/src/Neo.GUI/GUI/ImportCustomContractDialog.zh-Hans.resx @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + 83, 16 + + + 私钥(可选): + + + 36, 44 + + + 59, 16 + + + 形参列表: + + + 101, 41 + + + 475, 23 + + + 脚本代码 + + + 确定 + + + 取消 + + + 101, 12 + + + 475, 23 + + + 导入自定义合约 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/ImportPrivateKeyDialog.cs b/src/Neo.GUI/GUI/ImportPrivateKeyDialog.cs new file mode 100644 index 0000000000..9379f45900 --- /dev/null +++ b/src/Neo.GUI/GUI/ImportPrivateKeyDialog.cs @@ -0,0 +1,41 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ImportPrivateKeyDialog.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Windows.Forms; + +namespace Neo.GUI +{ + internal partial class ImportPrivateKeyDialog : Form + { + public ImportPrivateKeyDialog() + { + InitializeComponent(); + } + + public string[] WifStrings + { + get + { + return textBox1.Lines; + } + set + { + textBox1.Lines = value; + } + } + + private void textBox1_TextChanged(object sender, EventArgs e) + { + button1.Enabled = textBox1.TextLength > 0; + } + } +} diff --git a/src/Neo.GUI/GUI/ImportPrivateKeyDialog.designer.cs b/src/Neo.GUI/GUI/ImportPrivateKeyDialog.designer.cs new file mode 100644 index 0000000000..06b1001288 --- /dev/null +++ b/src/Neo.GUI/GUI/ImportPrivateKeyDialog.designer.cs @@ -0,0 +1,102 @@ +// Copyright (C) 2016-2024 The Neo Project. +// +// The neo-gui is free software distributed under the MIT software +// license, see the accompanying file LICENSE in the main directory of +// the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.GUI +{ + partial class ImportPrivateKeyDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ImportPrivateKeyDialog)); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.button1 = new System.Windows.Forms.Button(); + this.button2 = new System.Windows.Forms.Button(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.groupBox1.SuspendLayout(); + this.SuspendLayout(); + // + // textBox1 + // + resources.ApplyResources(this.textBox1, "textBox1"); + this.textBox1.Name = "textBox1"; + this.textBox1.TextChanged += new System.EventHandler(this.textBox1_TextChanged); + // + // button1 + // + resources.ApplyResources(this.button1, "button1"); + this.button1.DialogResult = System.Windows.Forms.DialogResult.OK; + this.button1.Name = "button1"; + this.button1.UseVisualStyleBackColor = true; + // + // button2 + // + resources.ApplyResources(this.button2, "button2"); + this.button2.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.button2.Name = "button2"; + this.button2.UseVisualStyleBackColor = true; + // + // groupBox1 + // + resources.ApplyResources(this.groupBox1, "groupBox1"); + this.groupBox1.Controls.Add(this.textBox1); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.TabStop = false; + // + // ImportPrivateKeyDialog + // + this.AcceptButton = this.button1; + resources.ApplyResources(this, "$this"); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.button2; + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.button2); + this.Controls.Add(this.button1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "ImportPrivateKeyDialog"; + this.ShowInTaskbar = false; + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.Button button2; + private System.Windows.Forms.GroupBox groupBox1; + } +} diff --git a/src/Neo.GUI/GUI/ImportPrivateKeyDialog.es-ES.resx b/src/Neo.GUI/GUI/ImportPrivateKeyDialog.es-ES.resx new file mode 100644 index 0000000000..86ff978402 --- /dev/null +++ b/src/Neo.GUI/GUI/ImportPrivateKeyDialog.es-ES.resx @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancelar + + + Clave privada WIF: + + + + NoControl + + + Aceptar + + + Importar clave privada + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/ImportPrivateKeyDialog.resx b/src/Neo.GUI/GUI/ImportPrivateKeyDialog.resx new file mode 100644 index 0000000000..5cf34443a4 --- /dev/null +++ b/src/Neo.GUI/GUI/ImportPrivateKeyDialog.resx @@ -0,0 +1,267 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Fill + + + + 3, 19 + + + + True + + + Vertical + + + 454, 79 + + + 0 + + + False + + + textBox1 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox1 + + + 0 + + + Bottom, Right + + + False + + + 316, 119 + + + 75, 23 + + + 1 + + + OK + + + button1 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 2 + + + Bottom, Right + + + 397, 119 + + + 75, 23 + + + 2 + + + Cancel + + + button2 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 1 + + + Top, Bottom, Left, Right + + + 12, 12 + + + 460, 101 + + + 0 + + + WIF Private Key: + + + groupBox1 + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 0 + + + True + + + 7, 17 + + + 484, 154 + + + 微软雅黑, 9pt + + + 3, 4, 3, 4 + + + CenterScreen + + + Import Private Key + + + ImportPrivateKeyDialog + + + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/ImportPrivateKeyDialog.zh-Hans.resx b/src/Neo.GUI/GUI/ImportPrivateKeyDialog.zh-Hans.resx new file mode 100644 index 0000000000..5db4bf12ea --- /dev/null +++ b/src/Neo.GUI/GUI/ImportPrivateKeyDialog.zh-Hans.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 确定 + + + 取消 + + + WIF私钥: + + + 导入私钥 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/InformationBox.Designer.cs b/src/Neo.GUI/GUI/InformationBox.Designer.cs new file mode 100644 index 0000000000..92bf89bc78 --- /dev/null +++ b/src/Neo.GUI/GUI/InformationBox.Designer.cs @@ -0,0 +1,100 @@ +// Copyright (C) 2016-2024 The Neo Project. +// +// The neo-gui is free software distributed under the MIT software +// license, see the accompanying file LICENSE in the main directory of +// the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.GUI +{ + partial class InformationBox + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(InformationBox)); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.button1 = new System.Windows.Forms.Button(); + this.button2 = new System.Windows.Forms.Button(); + this.label1 = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // textBox1 + // + resources.ApplyResources(this.textBox1, "textBox1"); + this.textBox1.Name = "textBox1"; + this.textBox1.ReadOnly = true; + // + // button1 + // + resources.ApplyResources(this.button1, "button1"); + this.button1.Name = "button1"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // button2 + // + resources.ApplyResources(this.button2, "button2"); + this.button2.DialogResult = System.Windows.Forms.DialogResult.OK; + this.button2.Name = "button2"; + this.button2.UseVisualStyleBackColor = true; + // + // label1 + // + resources.ApplyResources(this.label1, "label1"); + this.label1.Name = "label1"; + // + // InformationBox + // + this.AcceptButton = this.button2; + resources.ApplyResources(this, "$this"); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.button2; + this.Controls.Add(this.label1); + this.Controls.Add(this.button2); + this.Controls.Add(this.button1); + this.Controls.Add(this.textBox1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "InformationBox"; + this.ShowInTaskbar = false; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.Button button2; + private System.Windows.Forms.Label label1; + } +} diff --git a/src/Neo.GUI/GUI/InformationBox.cs b/src/Neo.GUI/GUI/InformationBox.cs new file mode 100644 index 0000000000..41516fd9db --- /dev/null +++ b/src/Neo.GUI/GUI/InformationBox.cs @@ -0,0 +1,44 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// InformationBox.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Windows.Forms; + +namespace Neo.GUI +{ + internal partial class InformationBox : Form + { + public InformationBox() + { + InitializeComponent(); + } + + public static DialogResult Show(string text, string message = null, string title = null) + { + using InformationBox box = new InformationBox(); + box.textBox1.Text = text; + if (message != null) + { + box.label1.Text = message; + } + if (title != null) + { + box.Text = title; + } + return box.ShowDialog(); + } + + private void button1_Click(object sender, System.EventArgs e) + { + textBox1.SelectAll(); + textBox1.Copy(); + } + } +} diff --git a/src/Neo.GUI/GUI/InformationBox.es-ES.resx b/src/Neo.GUI/GUI/InformationBox.es-ES.resx new file mode 100644 index 0000000000..1af985f64c --- /dev/null +++ b/src/Neo.GUI/GUI/InformationBox.es-ES.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Copiar + + + Cancelar + + + Información + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/InformationBox.resx b/src/Neo.GUI/GUI/InformationBox.resx new file mode 100644 index 0000000000..3aec9d5ab6 --- /dev/null +++ b/src/Neo.GUI/GUI/InformationBox.resx @@ -0,0 +1,255 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Top, Bottom, Left, Right + + + + 12, 29 + + + + True + + + Vertical + + + 489, 203 + + + 0 + + + textBox1 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 3 + + + Bottom, Right + + + 345, 238 + + + 75, 23 + + + 1 + + + copy + + + button1 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 2 + + + Bottom, Right + + + 426, 238 + + + 75, 23 + + + 2 + + + close + + + button2 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 1 + + + True + + + 12, 9 + + + 0, 17 + + + 3 + + + label1 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 0 + + + True + + + 7, 17 + + + 513, 273 + + + 微软雅黑, 9pt + + + CenterScreen + + + InformationBox + + + InformationBox + + + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/InformationBox.zh-Hans.resx b/src/Neo.GUI/GUI/InformationBox.zh-Hans.resx new file mode 100644 index 0000000000..ab3b23dc17 --- /dev/null +++ b/src/Neo.GUI/GUI/InformationBox.zh-Hans.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 复制 + + + 关闭 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/InputBox.Designer.cs b/src/Neo.GUI/GUI/InputBox.Designer.cs new file mode 100644 index 0000000000..163438b8e7 --- /dev/null +++ b/src/Neo.GUI/GUI/InputBox.Designer.cs @@ -0,0 +1,102 @@ +// Copyright (C) 2016-2024 The Neo Project. +// +// The neo-gui is free software distributed under the MIT software +// license, see the accompanying file LICENSE in the main directory of +// the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.GUI +{ + partial class InputBox + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(InputBox)); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.button1 = new System.Windows.Forms.Button(); + this.button2 = new System.Windows.Forms.Button(); + this.groupBox1.SuspendLayout(); + this.SuspendLayout(); + // + // textBox1 + // + resources.ApplyResources(this.textBox1, "textBox1"); + this.textBox1.Name = "textBox1"; + // + // groupBox1 + // + resources.ApplyResources(this.groupBox1, "groupBox1"); + this.groupBox1.Controls.Add(this.textBox1); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.TabStop = false; + // + // button1 + // + resources.ApplyResources(this.button1, "button1"); + this.button1.DialogResult = System.Windows.Forms.DialogResult.OK; + this.button1.Name = "button1"; + this.button1.UseVisualStyleBackColor = true; + // + // button2 + // + resources.ApplyResources(this.button2, "button2"); + this.button2.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.button2.Name = "button2"; + this.button2.UseVisualStyleBackColor = true; + // + // InputBox + // + this.AcceptButton = this.button1; + resources.ApplyResources(this, "$this"); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.button2; + this.Controls.Add(this.button2); + this.Controls.Add(this.button1); + this.Controls.Add(this.groupBox1); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "InputBox"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.Button button2; + } +} diff --git a/src/Neo.GUI/GUI/InputBox.cs b/src/Neo.GUI/GUI/InputBox.cs new file mode 100644 index 0000000000..8f301a8c99 --- /dev/null +++ b/src/Neo.GUI/GUI/InputBox.cs @@ -0,0 +1,33 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// InputBox.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Windows.Forms; + +namespace Neo.GUI +{ + internal partial class InputBox : Form + { + private InputBox(string text, string caption, string content) + { + InitializeComponent(); + Text = caption; + groupBox1.Text = text; + textBox1.Text = content; + } + + public static string Show(string text, string caption, string content = "") + { + using InputBox dialog = new InputBox(text, caption, content); + if (dialog.ShowDialog() != DialogResult.OK) return null; + return dialog.textBox1.Text; + } + } +} diff --git a/src/Neo.GUI/GUI/InputBox.es-ES.resx b/src/Neo.GUI/GUI/InputBox.es-ES.resx new file mode 100644 index 0000000000..3e13191c48 --- /dev/null +++ b/src/Neo.GUI/GUI/InputBox.es-ES.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Aceptar + + + Cancelar + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/InputBox.resx b/src/Neo.GUI/GUI/InputBox.resx new file mode 100644 index 0000000000..84533a5c93 --- /dev/null +++ b/src/Neo.GUI/GUI/InputBox.resx @@ -0,0 +1,249 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + 2 + + + True + + + 0 + + + + 7, 17 + + + InputBox + + + 75, 23 + + + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + button2 + + + InputBox + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox1 + + + 390, 118 + + + 420, 193 + + + 2 + + + + CenterScreen + + + 75, 23 + + + 2, 2, 2, 2 + + + groupBox1 + + + 0 + + + 3, 19 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 12, 12 + + + 1 + + + 0 + + + Fill + + + $this + + + groupBox1 + + + 0 + + + 1 + + + button1 + + + textBox1 + + + OK + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 252, 158 + + + Microsoft YaHei UI, 9pt + + + 396, 140 + + + 333, 158 + + + Cancel + + + $this + + + True + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/InputBox.zh-Hans.resx b/src/Neo.GUI/GUI/InputBox.zh-Hans.resx new file mode 100644 index 0000000000..0ede664604 --- /dev/null +++ b/src/Neo.GUI/GUI/InputBox.zh-Hans.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 确定 + + + 取消 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/InvokeContractDialog.Designer.cs b/src/Neo.GUI/GUI/InvokeContractDialog.Designer.cs new file mode 100644 index 0000000000..44454c0306 --- /dev/null +++ b/src/Neo.GUI/GUI/InvokeContractDialog.Designer.cs @@ -0,0 +1,270 @@ +// Copyright (C) 2016-2024 The Neo Project. +// +// The neo-gui is free software distributed under the MIT software +// license, see the accompanying file LICENSE in the main directory of +// the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.GUI +{ + partial class InvokeContractDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(InvokeContractDialog)); + this.button6 = new System.Windows.Forms.Button(); + this.textBox6 = new System.Windows.Forms.TextBox(); + this.button3 = new System.Windows.Forms.Button(); + this.button4 = new System.Windows.Forms.Button(); + this.label6 = new System.Windows.Forms.Label(); + this.label7 = new System.Windows.Forms.Label(); + this.button5 = new System.Windows.Forms.Button(); + this.openFileDialog1 = new System.Windows.Forms.OpenFileDialog(); + this.tabControl1 = new System.Windows.Forms.TabControl(); + this.tabPage3 = new System.Windows.Forms.TabPage(); + this.button8 = new System.Windows.Forms.Button(); + this.textBox9 = new System.Windows.Forms.TextBox(); + this.label10 = new System.Windows.Forms.Label(); + this.comboBox1 = new System.Windows.Forms.ComboBox(); + this.label9 = new System.Windows.Forms.Label(); + this.button7 = new System.Windows.Forms.Button(); + this.textBox8 = new System.Windows.Forms.TextBox(); + this.label8 = new System.Windows.Forms.Label(); + this.tabPage2 = new System.Windows.Forms.TabPage(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.textBox7 = new System.Windows.Forms.TextBox(); + this.openFileDialog2 = new System.Windows.Forms.OpenFileDialog(); + this.tabControl1.SuspendLayout(); + this.tabPage3.SuspendLayout(); + this.tabPage2.SuspendLayout(); + this.groupBox1.SuspendLayout(); + this.SuspendLayout(); + // + // button6 + // + resources.ApplyResources(this.button6, "button6"); + this.button6.Name = "button6"; + this.button6.UseVisualStyleBackColor = true; + this.button6.Click += new System.EventHandler(this.button6_Click); + // + // textBox6 + // + resources.ApplyResources(this.textBox6, "textBox6"); + this.textBox6.Name = "textBox6"; + this.textBox6.TextChanged += new System.EventHandler(this.textBox6_TextChanged); + // + // button3 + // + resources.ApplyResources(this.button3, "button3"); + this.button3.DialogResult = System.Windows.Forms.DialogResult.OK; + this.button3.Name = "button3"; + this.button3.UseVisualStyleBackColor = true; + // + // button4 + // + resources.ApplyResources(this.button4, "button4"); + this.button4.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.button4.Name = "button4"; + this.button4.UseVisualStyleBackColor = true; + // + // label6 + // + resources.ApplyResources(this.label6, "label6"); + this.label6.Name = "label6"; + // + // label7 + // + resources.ApplyResources(this.label7, "label7"); + this.label7.Name = "label7"; + // + // button5 + // + resources.ApplyResources(this.button5, "button5"); + this.button5.Name = "button5"; + this.button5.UseVisualStyleBackColor = true; + this.button5.Click += new System.EventHandler(this.button5_Click); + // + // openFileDialog1 + // + resources.ApplyResources(this.openFileDialog1, "openFileDialog1"); + this.openFileDialog1.DefaultExt = "avm"; + // + // tabControl1 + // + resources.ApplyResources(this.tabControl1, "tabControl1"); + this.tabControl1.Controls.Add(this.tabPage3); + this.tabControl1.Controls.Add(this.tabPage2); + this.tabControl1.Name = "tabControl1"; + this.tabControl1.SelectedIndex = 0; + // + // tabPage3 + // + resources.ApplyResources(this.tabPage3, "tabPage3"); + this.tabPage3.Controls.Add(this.button8); + this.tabPage3.Controls.Add(this.textBox9); + this.tabPage3.Controls.Add(this.label10); + this.tabPage3.Controls.Add(this.comboBox1); + this.tabPage3.Controls.Add(this.label9); + this.tabPage3.Controls.Add(this.button7); + this.tabPage3.Controls.Add(this.textBox8); + this.tabPage3.Controls.Add(this.label8); + this.tabPage3.Name = "tabPage3"; + this.tabPage3.UseVisualStyleBackColor = true; + // + // button8 + // + resources.ApplyResources(this.button8, "button8"); + this.button8.Name = "button8"; + this.button8.UseVisualStyleBackColor = true; + this.button8.Click += new System.EventHandler(this.button8_Click); + // + // textBox9 + // + resources.ApplyResources(this.textBox9, "textBox9"); + this.textBox9.Name = "textBox9"; + this.textBox9.ReadOnly = true; + // + // label10 + // + resources.ApplyResources(this.label10, "label10"); + this.label10.Name = "label10"; + // + // comboBox1 + // + resources.ApplyResources(this.comboBox1, "comboBox1"); + this.comboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboBox1.FormattingEnabled = true; + this.comboBox1.Name = "comboBox1"; + this.comboBox1.SelectedIndexChanged += new System.EventHandler(this.comboBox1_SelectedIndexChanged); + // + // label9 + // + resources.ApplyResources(this.label9, "label9"); + this.label9.Name = "label9"; + // + // button7 + // + resources.ApplyResources(this.button7, "button7"); + this.button7.Name = "button7"; + this.button7.UseVisualStyleBackColor = true; + this.button7.Click += new System.EventHandler(this.button7_Click); + // + // textBox8 + // + resources.ApplyResources(this.textBox8, "textBox8"); + this.textBox8.Name = "textBox8"; + this.textBox8.ReadOnly = true; + // + // label8 + // + resources.ApplyResources(this.label8, "label8"); + this.label8.Name = "label8"; + // + // tabPage2 + // + resources.ApplyResources(this.tabPage2, "tabPage2"); + this.tabPage2.Controls.Add(this.button6); + this.tabPage2.Controls.Add(this.textBox6); + this.tabPage2.Name = "tabPage2"; + this.tabPage2.UseVisualStyleBackColor = true; + // + // groupBox1 + // + resources.ApplyResources(this.groupBox1, "groupBox1"); + this.groupBox1.Controls.Add(this.textBox7); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.TabStop = false; + // + // textBox7 + // + resources.ApplyResources(this.textBox7, "textBox7"); + this.textBox7.Name = "textBox7"; + this.textBox7.ReadOnly = true; + // + // openFileDialog2 + // + resources.ApplyResources(this.openFileDialog2, "openFileDialog2"); + this.openFileDialog2.DefaultExt = "abi.json"; + // + // InvokeContractDialog + // + resources.ApplyResources(this, "$this"); + this.AcceptButton = this.button3; + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.button4; + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.tabControl1); + this.Controls.Add(this.button5); + this.Controls.Add(this.label7); + this.Controls.Add(this.label6); + this.Controls.Add(this.button4); + this.Controls.Add(this.button3); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "InvokeContractDialog"; + this.ShowInTaskbar = false; + this.tabControl1.ResumeLayout(false); + this.tabPage3.ResumeLayout(false); + this.tabPage3.PerformLayout(); + this.tabPage2.ResumeLayout(false); + this.tabPage2.PerformLayout(); + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + private System.Windows.Forms.TextBox textBox6; + private System.Windows.Forms.Button button3; + private System.Windows.Forms.Button button4; + private System.Windows.Forms.Label label6; + private System.Windows.Forms.Label label7; + private System.Windows.Forms.Button button5; + private System.Windows.Forms.Button button6; + private System.Windows.Forms.OpenFileDialog openFileDialog1; + private System.Windows.Forms.TabControl tabControl1; + private System.Windows.Forms.TabPage tabPage2; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.TextBox textBox7; + private System.Windows.Forms.TabPage tabPage3; + private System.Windows.Forms.TextBox textBox8; + private System.Windows.Forms.Label label8; + private System.Windows.Forms.Button button7; + private System.Windows.Forms.OpenFileDialog openFileDialog2; + private System.Windows.Forms.Label label9; + private System.Windows.Forms.ComboBox comboBox1; + private System.Windows.Forms.Button button8; + private System.Windows.Forms.TextBox textBox9; + private System.Windows.Forms.Label label10; + } +} diff --git a/src/Neo.GUI/GUI/InvokeContractDialog.cs b/src/Neo.GUI/GUI/InvokeContractDialog.cs new file mode 100644 index 0000000000..d8a8a6b66f --- /dev/null +++ b/src/Neo.GUI/GUI/InvokeContractDialog.cs @@ -0,0 +1,146 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// InvokeContractDialog.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using Neo.Network.P2P.Payloads; +using Neo.Properties; +using Neo.SmartContract; +using Neo.VM; +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Windows.Forms; +using static Neo.Program; + +namespace Neo.GUI +{ + internal partial class InvokeContractDialog : Form + { + private readonly Transaction tx; + private JObject abi; + private UInt160 script_hash; + private ContractParameter[] parameters; + + public InvokeContractDialog() + { + InitializeComponent(); + } + + public InvokeContractDialog(Transaction tx) : this() + { + this.tx = tx; + tabControl1.SelectedTab = tabPage2; + textBox6.Text = tx.Script.Span.ToHexString(); + textBox6.ReadOnly = true; + } + + public InvokeContractDialog(byte[] script) : this() + { + tabControl1.SelectedTab = tabPage2; + textBox6.Text = script.ToHexString(); + } + + public Transaction GetTransaction() + { + byte[] script = textBox6.Text.Trim().HexToBytes(); + return tx ?? Service.CurrentWallet.MakeTransaction(Service.NeoSystem.StoreView, script); + } + + private void UpdateScript() + { + using ScriptBuilder sb = new ScriptBuilder(); + sb.EmitDynamicCall(script_hash, (string)comboBox1.SelectedItem, parameters); + textBox6.Text = sb.ToArray().ToHexString(); + } + + private void textBox6_TextChanged(object sender, EventArgs e) + { + button3.Enabled = false; + button5.Enabled = textBox6.TextLength > 0; + } + + private void button5_Click(object sender, EventArgs e) + { + byte[] script; + try + { + script = textBox6.Text.Trim().HexToBytes(); + } + catch (FormatException ex) + { + MessageBox.Show(ex.Message); + return; + } + Transaction tx_test = tx ?? new Transaction + { + Signers = new Signer[0], + Attributes = new TransactionAttribute[0], + Script = script, + Witnesses = new Witness[0] + }; + using ApplicationEngine engine = ApplicationEngine.Run(tx_test.Script, Service.NeoSystem.StoreView, container: tx_test); + StringBuilder sb = new StringBuilder(); + sb.AppendLine($"VM State: {engine.State}"); + sb.AppendLine($"Gas Consumed: {engine.FeeConsumed}"); + sb.AppendLine($"Evaluation Stack: {new JArray(engine.ResultStack.Select(p => p.ToParameter().ToJson()))}"); + textBox7.Text = sb.ToString(); + if (engine.State != VMState.FAULT) + { + label7.Text = engine.FeeConsumed + " gas"; + button3.Enabled = true; + } + else + { + MessageBox.Show(Strings.ExecutionFailed); + } + } + + private void button6_Click(object sender, EventArgs e) + { + if (openFileDialog1.ShowDialog() != DialogResult.OK) return; + textBox6.Text = File.ReadAllBytes(openFileDialog1.FileName).ToHexString(); + } + + private void button7_Click(object sender, EventArgs e) + { + if (openFileDialog2.ShowDialog() != DialogResult.OK) return; + abi = (JObject)JToken.Parse(File.ReadAllText(openFileDialog2.FileName)); + script_hash = UInt160.Parse(abi["hash"].AsString()); + textBox8.Text = script_hash.ToString(); + comboBox1.Items.Clear(); + comboBox1.Items.AddRange(((JArray)abi["functions"]).Select(p => p["name"].AsString()).Where(p => p != abi["entrypoint"].AsString()).ToArray()); + textBox9.Clear(); + button8.Enabled = false; + } + + private void button8_Click(object sender, EventArgs e) + { + using (ParametersEditor dialog = new ParametersEditor(parameters)) + { + dialog.ShowDialog(); + } + UpdateScript(); + } + + private void comboBox1_SelectedIndexChanged(object sender, EventArgs e) + { + if (!(comboBox1.SelectedItem is string method)) return; + JArray functions = (JArray)abi["functions"]; + var function = functions.First(p => p["name"].AsString() == method); + JArray _params = (JArray)function["parameters"]; + parameters = _params.Select(p => new ContractParameter(p["type"].AsEnum())).ToArray(); + textBox9.Text = string.Join(", ", _params.Select(p => p["name"].AsString())); + button8.Enabled = parameters.Length > 0; + UpdateScript(); + } + } +} diff --git a/src/Neo.GUI/GUI/InvokeContractDialog.es-ES.resx b/src/Neo.GUI/GUI/InvokeContractDialog.es-ES.resx new file mode 100644 index 0000000000..20f5cf4799 --- /dev/null +++ b/src/Neo.GUI/GUI/InvokeContractDialog.es-ES.resx @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cargar + + + Invocar + + + Cancelar + + + + 38, 17 + + + Tasa: + + + 79, 17 + + + no evaluada + + + Prueba + + + 78, 17 + + + Parámetros: + + + Invocar contrato + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/InvokeContractDialog.resx b/src/Neo.GUI/GUI/InvokeContractDialog.resx new file mode 100644 index 0000000000..df3c2f0ab5 --- /dev/null +++ b/src/Neo.GUI/GUI/InvokeContractDialog.resx @@ -0,0 +1,735 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Bottom, Right + + + + 376, 190 + + + 75, 23 + + + + 1 + + + Load + + + button6 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tabPage2 + + + 0 + + + Top, Bottom, Left, Right + + + 6, 6 + + + True + + + Vertical + + + 445, 178 + + + 0 + + + textBox6 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tabPage2 + + + 1 + + + Bottom, Right + + + False + + + 321, 482 + + + 75, 23 + + + 6 + + + Invoke + + + button3 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 6 + + + Bottom, Right + + + NoControl + + + 402, 482 + + + 75, 23 + + + 7 + + + Cancel + + + button4 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 5 + + + Bottom, Left + + + True + + + 12, 482 + + + 31, 17 + + + 3 + + + Fee: + + + label6 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 4 + + + Bottom, Left + + + True + + + 49, 482 + + + 87, 17 + + + 4 + + + not evaluated + + + label7 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 3 + + + Bottom, Right + + + False + + + 240, 482 + + + 75, 23 + + + 5 + + + Test + + + button5 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 2 + + + 17, 17 + + + AVM File|*.avm + + + Top, Right + + + False + + + NoControl + + + 426, 67 + + + 25, 25 + + + 17 + + + ... + + + button8 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tabPage3 + + + 0 + + + Top, Left, Right + + + 89, 68 + + + 331, 23 + + + 16 + + + textBox9 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tabPage3 + + + 1 + + + True + + + NoControl + + + 6, 71 + + + 77, 17 + + + 15 + + + Parameters: + + + label10 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tabPage3 + + + 2 + + + 89, 36 + + + 362, 25 + + + 14 + + + comboBox1 + + + System.Windows.Forms.ComboBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tabPage3 + + + 3 + + + True + + + NoControl + + + 26, 39 + + + 57, 17 + + + 13 + + + Method: + + + label9 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tabPage3 + + + 4 + + + Bottom, Right + + + NoControl + + + 354, 188 + + + 97, 25 + + + 12 + + + Open ABI File + + + button7 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tabPage3 + + + 5 + + + Top, Left, Right + + + 89, 7 + + + 362, 23 + + + 2 + + + textBox8 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tabPage3 + + + 6 + + + True + + + NoControl + + + 10, 10 + + + 73, 17 + + + 1 + + + ScriptHash: + + + label8 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tabPage3 + + + 7 + + + 4, 26 + + + 3, 3, 3, 3 + + + 457, 219 + + + 2 + + + ABI + + + tabPage3 + + + System.Windows.Forms.TabPage, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tabControl1 + + + 0 + + + 4, 26 + + + 3, 3, 3, 3 + + + 457, 219 + + + 1 + + + Custom + + + tabPage2 + + + System.Windows.Forms.TabPage, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tabControl1 + + + 1 + + + 12, 12 + + + 465, 249 + + + 8 + + + tabControl1 + + + System.Windows.Forms.TabControl, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 1 + + + Fill + + + 3, 19 + + + True + + + Both + + + 459, 184 + + + 0 + + + False + + + textBox7 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox1 + + + 0 + + + 12, 267 + + + 465, 206 + + + 9 + + + Results + + + groupBox1 + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 0 + + + 165, 17 + + + ABI File|*.abi.json + + + True + + + 7, 17 + + + 489, 514 + + + 微软雅黑, 9pt + + + 3, 4, 3, 4 + + + CenterScreen + + + Invoke Contract + + + openFileDialog1 + + + System.Windows.Forms.OpenFileDialog, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + openFileDialog2 + + + System.Windows.Forms.OpenFileDialog, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + InvokeContractDialog + + + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/InvokeContractDialog.zh-Hans.resx b/src/Neo.GUI/GUI/InvokeContractDialog.zh-Hans.resx new file mode 100644 index 0000000000..d39deccbfa --- /dev/null +++ b/src/Neo.GUI/GUI/InvokeContractDialog.zh-Hans.resx @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 加载 + + + 调用 + + + 取消 + + + + 47, 17 + + + 手续费: + + + 65, 482 + + + 44, 17 + + + 未评估 + + + 试运行 + + + AVM文件|*.avm + + + 24, 71 + + + 59, 17 + + + 参数列表: + + + 48, 39 + + + 35, 17 + + + 方法: + + + 打开ABI文件 + + + 自定义 + + + 运行结果 + + + ABI文件|*.abi.json + + + 调用合约 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/MainForm.Designer.cs b/src/Neo.GUI/GUI/MainForm.Designer.cs new file mode 100644 index 0000000000..4ea5e2e5f6 --- /dev/null +++ b/src/Neo.GUI/GUI/MainForm.Designer.cs @@ -0,0 +1,732 @@ +// Copyright (C) 2016-2024 The Neo Project. +// +// The neo-gui is free software distributed under the MIT software +// license, see the accompanying file LICENSE in the main directory of +// the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.GUI +{ + partial class MainForm + { + /// + /// 必需的设计器变量。 + /// + private System.ComponentModel.IContainer components = null; + + /// + /// 清理所有正在使用的资源。 + /// + /// 如果应释放托管资源,为 true;否则为 false。 + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows 窗体设计器生成的代码 + + /// + /// 设计器支持所需的方法 - 不要 + /// 使用代码编辑器修改此方法的内容。 + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); + this.menuStrip1 = new System.Windows.Forms.MenuStrip(); + this.钱包WToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.创建钱包数据库NToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.打开钱包数据库OToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); + this.修改密码CToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); + this.退出XToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.交易TToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.转账TToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator5 = new System.Windows.Forms.ToolStripSeparator(); + this.签名SToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.高级AToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.deployContractToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.invokeContractToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator11 = new System.Windows.Forms.ToolStripSeparator(); + this.选举EToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.signDataToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator9 = new System.Windows.Forms.ToolStripSeparator(); + this.optionsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.帮助HToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.查看帮助VToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.官网WToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator(); + this.开发人员工具TToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.consoleToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator4 = new System.Windows.Forms.ToolStripSeparator(); + this.关于AntSharesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.listView1 = new System.Windows.Forms.ListView(); + this.columnHeader1 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnHeader4 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnHeader11 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components); + this.创建新地址NToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.导入私钥IToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.importWIFToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator10 = new System.Windows.Forms.ToolStripSeparator(); + this.importWatchOnlyAddressToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.创建智能合约SToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.多方签名MToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator12 = new System.Windows.Forms.ToolStripSeparator(); + this.自定义CToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator6 = new System.Windows.Forms.ToolStripSeparator(); + this.查看私钥VToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.viewContractToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.voteToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.复制到剪贴板CToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.删除DToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.statusStrip1 = new System.Windows.Forms.StatusStrip(); + this.toolStripStatusLabel1 = new System.Windows.Forms.ToolStripStatusLabel(); + this.lbl_height = new System.Windows.Forms.ToolStripStatusLabel(); + this.toolStripStatusLabel4 = new System.Windows.Forms.ToolStripStatusLabel(); + this.lbl_count_node = new System.Windows.Forms.ToolStripStatusLabel(); + this.toolStripProgressBar1 = new System.Windows.Forms.ToolStripProgressBar(); + this.toolStripStatusLabel2 = new System.Windows.Forms.ToolStripStatusLabel(); + this.toolStripStatusLabel3 = new System.Windows.Forms.ToolStripStatusLabel(); + this.timer1 = new System.Windows.Forms.Timer(this.components); + this.tabControl1 = new System.Windows.Forms.TabControl(); + this.tabPage1 = new System.Windows.Forms.TabPage(); + this.tabPage2 = new System.Windows.Forms.TabPage(); + this.listView2 = new System.Windows.Forms.ListView(); + this.columnHeader2 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnHeader6 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnHeader3 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnHeader5 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.tabPage3 = new System.Windows.Forms.TabPage(); + this.listView3 = new System.Windows.Forms.ListView(); + this.columnHeader7 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnHeader8 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnHeader9 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnHeader10 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.contextMenuStrip3 = new System.Windows.Forms.ContextMenuStrip(this.components); + this.toolStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem(); + this.menuStrip1.SuspendLayout(); + this.contextMenuStrip1.SuspendLayout(); + this.statusStrip1.SuspendLayout(); + this.tabControl1.SuspendLayout(); + this.tabPage1.SuspendLayout(); + this.tabPage2.SuspendLayout(); + this.tabPage3.SuspendLayout(); + this.contextMenuStrip3.SuspendLayout(); + this.SuspendLayout(); + // + // menuStrip1 + // + resources.ApplyResources(this.menuStrip1, "menuStrip1"); + this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.钱包WToolStripMenuItem, + this.交易TToolStripMenuItem, + this.高级AToolStripMenuItem, + this.帮助HToolStripMenuItem}); + this.menuStrip1.Name = "menuStrip1"; + // + // 钱包WToolStripMenuItem + // + resources.ApplyResources(this.钱包WToolStripMenuItem, "钱包WToolStripMenuItem"); + this.钱包WToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.创建钱包数据库NToolStripMenuItem, + this.打开钱包数据库OToolStripMenuItem, + this.toolStripSeparator1, + this.修改密码CToolStripMenuItem, + this.toolStripSeparator2, + this.退出XToolStripMenuItem}); + this.钱包WToolStripMenuItem.Name = "钱包WToolStripMenuItem"; + // + // 创建钱包数据库NToolStripMenuItem + // + resources.ApplyResources(this.创建钱包数据库NToolStripMenuItem, "创建钱包数据库NToolStripMenuItem"); + this.创建钱包数据库NToolStripMenuItem.Name = "创建钱包数据库NToolStripMenuItem"; + this.创建钱包数据库NToolStripMenuItem.Click += new System.EventHandler(this.创建钱包数据库NToolStripMenuItem_Click); + // + // 打开钱包数据库OToolStripMenuItem + // + resources.ApplyResources(this.打开钱包数据库OToolStripMenuItem, "打开钱包数据库OToolStripMenuItem"); + this.打开钱包数据库OToolStripMenuItem.Name = "打开钱包数据库OToolStripMenuItem"; + this.打开钱包数据库OToolStripMenuItem.Click += new System.EventHandler(this.打开钱包数据库OToolStripMenuItem_Click); + // + // toolStripSeparator1 + // + resources.ApplyResources(this.toolStripSeparator1, "toolStripSeparator1"); + this.toolStripSeparator1.Name = "toolStripSeparator1"; + // + // 修改密码CToolStripMenuItem + // + resources.ApplyResources(this.修改密码CToolStripMenuItem, "修改密码CToolStripMenuItem"); + this.修改密码CToolStripMenuItem.Name = "修改密码CToolStripMenuItem"; + this.修改密码CToolStripMenuItem.Click += new System.EventHandler(this.修改密码CToolStripMenuItem_Click); + // + // toolStripSeparator2 + // + resources.ApplyResources(this.toolStripSeparator2, "toolStripSeparator2"); + this.toolStripSeparator2.Name = "toolStripSeparator2"; + // + // 退出XToolStripMenuItem + // + resources.ApplyResources(this.退出XToolStripMenuItem, "退出XToolStripMenuItem"); + this.退出XToolStripMenuItem.Name = "退出XToolStripMenuItem"; + this.退出XToolStripMenuItem.Click += new System.EventHandler(this.退出XToolStripMenuItem_Click); + // + // 交易TToolStripMenuItem + // + resources.ApplyResources(this.交易TToolStripMenuItem, "交易TToolStripMenuItem"); + this.交易TToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.转账TToolStripMenuItem, + this.toolStripSeparator5, + this.签名SToolStripMenuItem}); + this.交易TToolStripMenuItem.Name = "交易TToolStripMenuItem"; + // + // 转账TToolStripMenuItem + // + resources.ApplyResources(this.转账TToolStripMenuItem, "转账TToolStripMenuItem"); + this.转账TToolStripMenuItem.Name = "转账TToolStripMenuItem"; + this.转账TToolStripMenuItem.Click += new System.EventHandler(this.转账TToolStripMenuItem_Click); + // + // toolStripSeparator5 + // + resources.ApplyResources(this.toolStripSeparator5, "toolStripSeparator5"); + this.toolStripSeparator5.Name = "toolStripSeparator5"; + // + // 签名SToolStripMenuItem + // + resources.ApplyResources(this.签名SToolStripMenuItem, "签名SToolStripMenuItem"); + this.签名SToolStripMenuItem.Name = "签名SToolStripMenuItem"; + this.签名SToolStripMenuItem.Click += new System.EventHandler(this.签名SToolStripMenuItem_Click); + // + // 高级AToolStripMenuItem + // + resources.ApplyResources(this.高级AToolStripMenuItem, "高级AToolStripMenuItem"); + this.高级AToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.deployContractToolStripMenuItem, + this.invokeContractToolStripMenuItem, + this.toolStripSeparator11, + this.选举EToolStripMenuItem, + this.signDataToolStripMenuItem, + this.toolStripSeparator9, + this.optionsToolStripMenuItem}); + this.高级AToolStripMenuItem.Name = "高级AToolStripMenuItem"; + // + // deployContractToolStripMenuItem + // + resources.ApplyResources(this.deployContractToolStripMenuItem, "deployContractToolStripMenuItem"); + this.deployContractToolStripMenuItem.Name = "deployContractToolStripMenuItem"; + this.deployContractToolStripMenuItem.Click += new System.EventHandler(this.deployContractToolStripMenuItem_Click); + // + // invokeContractToolStripMenuItem + // + resources.ApplyResources(this.invokeContractToolStripMenuItem, "invokeContractToolStripMenuItem"); + this.invokeContractToolStripMenuItem.Name = "invokeContractToolStripMenuItem"; + this.invokeContractToolStripMenuItem.Click += new System.EventHandler(this.invokeContractToolStripMenuItem_Click); + // + // toolStripSeparator11 + // + resources.ApplyResources(this.toolStripSeparator11, "toolStripSeparator11"); + this.toolStripSeparator11.Name = "toolStripSeparator11"; + // + // 选举EToolStripMenuItem + // + resources.ApplyResources(this.选举EToolStripMenuItem, "选举EToolStripMenuItem"); + this.选举EToolStripMenuItem.Name = "选举EToolStripMenuItem"; + this.选举EToolStripMenuItem.Click += new System.EventHandler(this.选举EToolStripMenuItem_Click); + // + // signDataToolStripMenuItem + // + resources.ApplyResources(this.signDataToolStripMenuItem, "signDataToolStripMenuItem"); + this.signDataToolStripMenuItem.Name = "signDataToolStripMenuItem"; + this.signDataToolStripMenuItem.Click += new System.EventHandler(this.signDataToolStripMenuItem_Click); + // + // toolStripSeparator9 + // + resources.ApplyResources(this.toolStripSeparator9, "toolStripSeparator9"); + this.toolStripSeparator9.Name = "toolStripSeparator9"; + // + // optionsToolStripMenuItem + // + resources.ApplyResources(this.optionsToolStripMenuItem, "optionsToolStripMenuItem"); + this.optionsToolStripMenuItem.Name = "optionsToolStripMenuItem"; + this.optionsToolStripMenuItem.Click += new System.EventHandler(this.optionsToolStripMenuItem_Click); + // + // 帮助HToolStripMenuItem + // + resources.ApplyResources(this.帮助HToolStripMenuItem, "帮助HToolStripMenuItem"); + this.帮助HToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.查看帮助VToolStripMenuItem, + this.官网WToolStripMenuItem, + this.toolStripSeparator3, + this.开发人员工具TToolStripMenuItem, + this.consoleToolStripMenuItem, + this.toolStripSeparator4, + this.关于AntSharesToolStripMenuItem}); + this.帮助HToolStripMenuItem.Name = "帮助HToolStripMenuItem"; + // + // 查看帮助VToolStripMenuItem + // + resources.ApplyResources(this.查看帮助VToolStripMenuItem, "查看帮助VToolStripMenuItem"); + this.查看帮助VToolStripMenuItem.Name = "查看帮助VToolStripMenuItem"; + // + // 官网WToolStripMenuItem + // + resources.ApplyResources(this.官网WToolStripMenuItem, "官网WToolStripMenuItem"); + this.官网WToolStripMenuItem.Name = "官网WToolStripMenuItem"; + this.官网WToolStripMenuItem.Click += new System.EventHandler(this.官网WToolStripMenuItem_Click); + // + // toolStripSeparator3 + // + resources.ApplyResources(this.toolStripSeparator3, "toolStripSeparator3"); + this.toolStripSeparator3.Name = "toolStripSeparator3"; + // + // 开发人员工具TToolStripMenuItem + // + resources.ApplyResources(this.开发人员工具TToolStripMenuItem, "开发人员工具TToolStripMenuItem"); + this.开发人员工具TToolStripMenuItem.Name = "开发人员工具TToolStripMenuItem"; + this.开发人员工具TToolStripMenuItem.Click += new System.EventHandler(this.开发人员工具TToolStripMenuItem_Click); + // + // consoleToolStripMenuItem + // + resources.ApplyResources(this.consoleToolStripMenuItem, "consoleToolStripMenuItem"); + this.consoleToolStripMenuItem.Name = "consoleToolStripMenuItem"; + this.consoleToolStripMenuItem.Click += new System.EventHandler(this.consoleToolStripMenuItem_Click); + // + // toolStripSeparator4 + // + resources.ApplyResources(this.toolStripSeparator4, "toolStripSeparator4"); + this.toolStripSeparator4.Name = "toolStripSeparator4"; + // + // 关于AntSharesToolStripMenuItem + // + resources.ApplyResources(this.关于AntSharesToolStripMenuItem, "关于AntSharesToolStripMenuItem"); + this.关于AntSharesToolStripMenuItem.Name = "关于AntSharesToolStripMenuItem"; + this.关于AntSharesToolStripMenuItem.Click += new System.EventHandler(this.关于AntSharesToolStripMenuItem_Click); + // + // listView1 + // + resources.ApplyResources(this.listView1, "listView1"); + this.listView1.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { + this.columnHeader1, + this.columnHeader4, + this.columnHeader11}); + this.listView1.ContextMenuStrip = this.contextMenuStrip1; + this.listView1.FullRowSelect = true; + this.listView1.GridLines = true; + this.listView1.Groups.AddRange(new System.Windows.Forms.ListViewGroup[] { + ((System.Windows.Forms.ListViewGroup)(resources.GetObject("listView1.Groups"))), + ((System.Windows.Forms.ListViewGroup)(resources.GetObject("listView1.Groups1"))), + ((System.Windows.Forms.ListViewGroup)(resources.GetObject("listView1.Groups2")))}); + this.listView1.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable; + this.listView1.HideSelection = false; + this.listView1.Name = "listView1"; + this.listView1.UseCompatibleStateImageBehavior = false; + this.listView1.View = System.Windows.Forms.View.Details; + this.listView1.DoubleClick += new System.EventHandler(this.listView1_DoubleClick); + // + // columnHeader1 + // + resources.ApplyResources(this.columnHeader1, "columnHeader1"); + // + // columnHeader4 + // + resources.ApplyResources(this.columnHeader4, "columnHeader4"); + // + // columnHeader11 + // + resources.ApplyResources(this.columnHeader11, "columnHeader11"); + // + // contextMenuStrip1 + // + resources.ApplyResources(this.contextMenuStrip1, "contextMenuStrip1"); + this.contextMenuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.创建新地址NToolStripMenuItem, + this.导入私钥IToolStripMenuItem, + this.创建智能合约SToolStripMenuItem, + this.toolStripSeparator6, + this.查看私钥VToolStripMenuItem, + this.viewContractToolStripMenuItem, + this.voteToolStripMenuItem, + this.复制到剪贴板CToolStripMenuItem, + this.删除DToolStripMenuItem}); + this.contextMenuStrip1.Name = "contextMenuStrip1"; + this.contextMenuStrip1.Opening += new System.ComponentModel.CancelEventHandler(this.contextMenuStrip1_Opening); + // + // 创建新地址NToolStripMenuItem + // + resources.ApplyResources(this.创建新地址NToolStripMenuItem, "创建新地址NToolStripMenuItem"); + this.创建新地址NToolStripMenuItem.Name = "创建新地址NToolStripMenuItem"; + this.创建新地址NToolStripMenuItem.Click += new System.EventHandler(this.创建新地址NToolStripMenuItem_Click); + // + // 导入私钥IToolStripMenuItem + // + resources.ApplyResources(this.导入私钥IToolStripMenuItem, "导入私钥IToolStripMenuItem"); + this.导入私钥IToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.importWIFToolStripMenuItem, + this.toolStripSeparator10, + this.importWatchOnlyAddressToolStripMenuItem}); + this.导入私钥IToolStripMenuItem.Name = "导入私钥IToolStripMenuItem"; + // + // importWIFToolStripMenuItem + // + resources.ApplyResources(this.importWIFToolStripMenuItem, "importWIFToolStripMenuItem"); + this.importWIFToolStripMenuItem.Name = "importWIFToolStripMenuItem"; + this.importWIFToolStripMenuItem.Click += new System.EventHandler(this.importWIFToolStripMenuItem_Click); + // + // toolStripSeparator10 + // + resources.ApplyResources(this.toolStripSeparator10, "toolStripSeparator10"); + this.toolStripSeparator10.Name = "toolStripSeparator10"; + // + // importWatchOnlyAddressToolStripMenuItem + // + resources.ApplyResources(this.importWatchOnlyAddressToolStripMenuItem, "importWatchOnlyAddressToolStripMenuItem"); + this.importWatchOnlyAddressToolStripMenuItem.Name = "importWatchOnlyAddressToolStripMenuItem"; + this.importWatchOnlyAddressToolStripMenuItem.Click += new System.EventHandler(this.importWatchOnlyAddressToolStripMenuItem_Click); + // + // 创建智能合约SToolStripMenuItem + // + resources.ApplyResources(this.创建智能合约SToolStripMenuItem, "创建智能合约SToolStripMenuItem"); + this.创建智能合约SToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.多方签名MToolStripMenuItem, + this.toolStripSeparator12, + this.自定义CToolStripMenuItem}); + this.创建智能合约SToolStripMenuItem.Name = "创建智能合约SToolStripMenuItem"; + // + // 多方签名MToolStripMenuItem + // + resources.ApplyResources(this.多方签名MToolStripMenuItem, "多方签名MToolStripMenuItem"); + this.多方签名MToolStripMenuItem.Name = "多方签名MToolStripMenuItem"; + this.多方签名MToolStripMenuItem.Click += new System.EventHandler(this.多方签名MToolStripMenuItem_Click); + // + // toolStripSeparator12 + // + resources.ApplyResources(this.toolStripSeparator12, "toolStripSeparator12"); + this.toolStripSeparator12.Name = "toolStripSeparator12"; + // + // 自定义CToolStripMenuItem + // + resources.ApplyResources(this.自定义CToolStripMenuItem, "自定义CToolStripMenuItem"); + this.自定义CToolStripMenuItem.Name = "自定义CToolStripMenuItem"; + this.自定义CToolStripMenuItem.Click += new System.EventHandler(this.自定义CToolStripMenuItem_Click); + // + // toolStripSeparator6 + // + resources.ApplyResources(this.toolStripSeparator6, "toolStripSeparator6"); + this.toolStripSeparator6.Name = "toolStripSeparator6"; + // + // 查看私钥VToolStripMenuItem + // + resources.ApplyResources(this.查看私钥VToolStripMenuItem, "查看私钥VToolStripMenuItem"); + this.查看私钥VToolStripMenuItem.Name = "查看私钥VToolStripMenuItem"; + this.查看私钥VToolStripMenuItem.Click += new System.EventHandler(this.查看私钥VToolStripMenuItem_Click); + // + // viewContractToolStripMenuItem + // + resources.ApplyResources(this.viewContractToolStripMenuItem, "viewContractToolStripMenuItem"); + this.viewContractToolStripMenuItem.Name = "viewContractToolStripMenuItem"; + this.viewContractToolStripMenuItem.Click += new System.EventHandler(this.viewContractToolStripMenuItem_Click); + // + // voteToolStripMenuItem + // + resources.ApplyResources(this.voteToolStripMenuItem, "voteToolStripMenuItem"); + this.voteToolStripMenuItem.Name = "voteToolStripMenuItem"; + this.voteToolStripMenuItem.Click += new System.EventHandler(this.voteToolStripMenuItem_Click); + // + // 复制到剪贴板CToolStripMenuItem + // + resources.ApplyResources(this.复制到剪贴板CToolStripMenuItem, "复制到剪贴板CToolStripMenuItem"); + this.复制到剪贴板CToolStripMenuItem.Name = "复制到剪贴板CToolStripMenuItem"; + this.复制到剪贴板CToolStripMenuItem.Click += new System.EventHandler(this.复制到剪贴板CToolStripMenuItem_Click); + // + // 删除DToolStripMenuItem + // + resources.ApplyResources(this.删除DToolStripMenuItem, "删除DToolStripMenuItem"); + this.删除DToolStripMenuItem.Name = "删除DToolStripMenuItem"; + this.删除DToolStripMenuItem.Click += new System.EventHandler(this.删除DToolStripMenuItem_Click); + // + // statusStrip1 + // + resources.ApplyResources(this.statusStrip1, "statusStrip1"); + this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.toolStripStatusLabel1, + this.lbl_height, + this.toolStripStatusLabel4, + this.lbl_count_node, + this.toolStripProgressBar1, + this.toolStripStatusLabel2, + this.toolStripStatusLabel3}); + this.statusStrip1.LayoutStyle = System.Windows.Forms.ToolStripLayoutStyle.HorizontalStackWithOverflow; + this.statusStrip1.Name = "statusStrip1"; + this.statusStrip1.SizingGrip = false; + // + // toolStripStatusLabel1 + // + resources.ApplyResources(this.toolStripStatusLabel1, "toolStripStatusLabel1"); + this.toolStripStatusLabel1.Name = "toolStripStatusLabel1"; + // + // lbl_height + // + resources.ApplyResources(this.lbl_height, "lbl_height"); + this.lbl_height.Name = "lbl_height"; + // + // toolStripStatusLabel4 + // + resources.ApplyResources(this.toolStripStatusLabel4, "toolStripStatusLabel4"); + this.toolStripStatusLabel4.Name = "toolStripStatusLabel4"; + // + // lbl_count_node + // + resources.ApplyResources(this.lbl_count_node, "lbl_count_node"); + this.lbl_count_node.Name = "lbl_count_node"; + // + // toolStripProgressBar1 + // + resources.ApplyResources(this.toolStripProgressBar1, "toolStripProgressBar1"); + this.toolStripProgressBar1.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right; + this.toolStripProgressBar1.Maximum = 15; + this.toolStripProgressBar1.Name = "toolStripProgressBar1"; + this.toolStripProgressBar1.Step = 1; + // + // toolStripStatusLabel2 + // + resources.ApplyResources(this.toolStripStatusLabel2, "toolStripStatusLabel2"); + this.toolStripStatusLabel2.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right; + this.toolStripStatusLabel2.Name = "toolStripStatusLabel2"; + // + // toolStripStatusLabel3 + // + resources.ApplyResources(this.toolStripStatusLabel3, "toolStripStatusLabel3"); + this.toolStripStatusLabel3.IsLink = true; + this.toolStripStatusLabel3.Name = "toolStripStatusLabel3"; + this.toolStripStatusLabel3.Click += new System.EventHandler(this.toolStripStatusLabel3_Click); + // + // timer1 + // + this.timer1.Enabled = true; + this.timer1.Interval = 500; + this.timer1.Tick += new System.EventHandler(this.timer1_Tick); + // + // tabControl1 + // + resources.ApplyResources(this.tabControl1, "tabControl1"); + this.tabControl1.Controls.Add(this.tabPage1); + this.tabControl1.Controls.Add(this.tabPage2); + this.tabControl1.Controls.Add(this.tabPage3); + this.tabControl1.Name = "tabControl1"; + this.tabControl1.SelectedIndex = 0; + // + // tabPage1 + // + resources.ApplyResources(this.tabPage1, "tabPage1"); + this.tabPage1.Controls.Add(this.listView1); + this.tabPage1.Name = "tabPage1"; + this.tabPage1.UseVisualStyleBackColor = true; + // + // tabPage2 + // + resources.ApplyResources(this.tabPage2, "tabPage2"); + this.tabPage2.Controls.Add(this.listView2); + this.tabPage2.Name = "tabPage2"; + this.tabPage2.UseVisualStyleBackColor = true; + // + // listView2 + // + resources.ApplyResources(this.listView2, "listView2"); + this.listView2.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { + this.columnHeader2, + this.columnHeader6, + this.columnHeader3, + this.columnHeader5}); + this.listView2.FullRowSelect = true; + this.listView2.GridLines = true; + this.listView2.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable; + this.listView2.HideSelection = false; + this.listView2.Name = "listView2"; + this.listView2.ShowGroups = false; + this.listView2.UseCompatibleStateImageBehavior = false; + this.listView2.View = System.Windows.Forms.View.Details; + this.listView2.DoubleClick += new System.EventHandler(this.listView2_DoubleClick); + // + // columnHeader2 + // + resources.ApplyResources(this.columnHeader2, "columnHeader2"); + // + // columnHeader6 + // + resources.ApplyResources(this.columnHeader6, "columnHeader6"); + // + // columnHeader3 + // + resources.ApplyResources(this.columnHeader3, "columnHeader3"); + // + // columnHeader5 + // + resources.ApplyResources(this.columnHeader5, "columnHeader5"); + // + // tabPage3 + // + resources.ApplyResources(this.tabPage3, "tabPage3"); + this.tabPage3.Controls.Add(this.listView3); + this.tabPage3.Name = "tabPage3"; + this.tabPage3.UseVisualStyleBackColor = true; + // + // listView3 + // + resources.ApplyResources(this.listView3, "listView3"); + this.listView3.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { + this.columnHeader7, + this.columnHeader8, + this.columnHeader9, + this.columnHeader10}); + this.listView3.ContextMenuStrip = this.contextMenuStrip3; + this.listView3.FullRowSelect = true; + this.listView3.GridLines = true; + this.listView3.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable; + this.listView3.HideSelection = false; + this.listView3.Name = "listView3"; + this.listView3.ShowGroups = false; + this.listView3.UseCompatibleStateImageBehavior = false; + this.listView3.View = System.Windows.Forms.View.Details; + this.listView3.DoubleClick += new System.EventHandler(this.listView3_DoubleClick); + // + // columnHeader7 + // + resources.ApplyResources(this.columnHeader7, "columnHeader7"); + // + // columnHeader8 + // + resources.ApplyResources(this.columnHeader8, "columnHeader8"); + // + // columnHeader9 + // + resources.ApplyResources(this.columnHeader9, "columnHeader9"); + // + // columnHeader10 + // + resources.ApplyResources(this.columnHeader10, "columnHeader10"); + // + // contextMenuStrip3 + // + resources.ApplyResources(this.contextMenuStrip3, "contextMenuStrip3"); + this.contextMenuStrip3.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.toolStripMenuItem1}); + this.contextMenuStrip3.Name = "contextMenuStrip3"; + // + // toolStripMenuItem1 + // + resources.ApplyResources(this.toolStripMenuItem1, "toolStripMenuItem1"); + this.toolStripMenuItem1.Name = "toolStripMenuItem1"; + this.toolStripMenuItem1.Click += new System.EventHandler(this.toolStripMenuItem1_Click); + // + // MainForm + // + resources.ApplyResources(this, "$this"); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.tabControl1); + this.Controls.Add(this.statusStrip1); + this.Controls.Add(this.menuStrip1); + this.MainMenuStrip = this.menuStrip1; + this.Name = "MainForm"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.MainForm_FormClosing); + this.Load += new System.EventHandler(this.MainForm_Load); + this.menuStrip1.ResumeLayout(false); + this.menuStrip1.PerformLayout(); + this.contextMenuStrip1.ResumeLayout(false); + this.statusStrip1.ResumeLayout(false); + this.statusStrip1.PerformLayout(); + this.tabControl1.ResumeLayout(false); + this.tabPage1.ResumeLayout(false); + this.tabPage2.ResumeLayout(false); + this.tabPage3.ResumeLayout(false); + this.contextMenuStrip3.ResumeLayout(false); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.MenuStrip menuStrip1; + private System.Windows.Forms.ToolStripMenuItem 钱包WToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem 创建钱包数据库NToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem 打开钱包数据库OToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator1; + private System.Windows.Forms.ToolStripMenuItem 修改密码CToolStripMenuItem; + private System.Windows.Forms.ColumnHeader columnHeader1; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator2; + private System.Windows.Forms.ToolStripMenuItem 退出XToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem 帮助HToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem 查看帮助VToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem 官网WToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator3; + private System.Windows.Forms.ToolStripMenuItem 开发人员工具TToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator4; + private System.Windows.Forms.ToolStripMenuItem 关于AntSharesToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem 交易TToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem 签名SToolStripMenuItem; + private System.Windows.Forms.ContextMenuStrip contextMenuStrip1; + private System.Windows.Forms.ToolStripMenuItem 创建新地址NToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem 导入私钥IToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator6; + private System.Windows.Forms.ToolStripMenuItem 查看私钥VToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem 复制到剪贴板CToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem 删除DToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator5; + private System.Windows.Forms.StatusStrip statusStrip1; + private System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabel1; + private System.Windows.Forms.ToolStripStatusLabel lbl_height; + private System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabel4; + private System.Windows.Forms.ToolStripStatusLabel lbl_count_node; + private System.Windows.Forms.Timer timer1; + private System.Windows.Forms.TabControl tabControl1; + private System.Windows.Forms.TabPage tabPage1; + private System.Windows.Forms.TabPage tabPage2; + private System.Windows.Forms.ListView listView2; + private System.Windows.Forms.ColumnHeader columnHeader2; + private System.Windows.Forms.ColumnHeader columnHeader3; + private System.Windows.Forms.ToolStripMenuItem 转账TToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem 高级AToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem 创建智能合约SToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem 多方签名MToolStripMenuItem; + private System.Windows.Forms.ListView listView1; + private System.Windows.Forms.ColumnHeader columnHeader5; + private System.Windows.Forms.ColumnHeader columnHeader6; + private System.Windows.Forms.ToolStripMenuItem importWIFToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem 选举EToolStripMenuItem; + private System.Windows.Forms.TabPage tabPage3; + private System.Windows.Forms.ListView listView3; + private System.Windows.Forms.ColumnHeader columnHeader7; + private System.Windows.Forms.ColumnHeader columnHeader8; + private System.Windows.Forms.ColumnHeader columnHeader9; + private System.Windows.Forms.ToolStripProgressBar toolStripProgressBar1; + private System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabel2; + private System.Windows.Forms.ToolStripMenuItem 自定义CToolStripMenuItem; + private System.Windows.Forms.ColumnHeader columnHeader10; + private System.Windows.Forms.ContextMenuStrip contextMenuStrip3; + private System.Windows.Forms.ToolStripMenuItem toolStripMenuItem1; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator9; + private System.Windows.Forms.ToolStripMenuItem optionsToolStripMenuItem; + private System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabel3; + private System.Windows.Forms.ToolStripMenuItem viewContractToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator10; + private System.Windows.Forms.ToolStripMenuItem importWatchOnlyAddressToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem voteToolStripMenuItem; + private System.Windows.Forms.ColumnHeader columnHeader4; + private System.Windows.Forms.ColumnHeader columnHeader11; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator11; + private System.Windows.Forms.ToolStripMenuItem deployContractToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem invokeContractToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator12; + private System.Windows.Forms.ToolStripMenuItem signDataToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem consoleToolStripMenuItem; + } +} + diff --git a/src/Neo.GUI/GUI/MainForm.cs b/src/Neo.GUI/GUI/MainForm.cs new file mode 100644 index 0000000000..3114e542b6 --- /dev/null +++ b/src/Neo.GUI/GUI/MainForm.cs @@ -0,0 +1,616 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// MainForm.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Akka.Actor; +using Neo.IO.Actors; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Properties; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using Neo.Wallets.NEP6; +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Numerics; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Windows.Forms; +using System.Xml.Linq; +using static Neo.Program; +using static Neo.SmartContract.Helper; +using VMArray = Neo.VM.Types.Array; + +namespace Neo.GUI +{ + internal partial class MainForm : Form + { + private bool check_nep5_balance = false; + private DateTime persistence_time = DateTime.MinValue; + private IActorRef actor; + + public MainForm(XDocument xdoc = null) + { + InitializeComponent(); + + toolStripProgressBar1.Maximum = (int)Service.NeoSystem.Settings.TimePerBlock.TotalSeconds; + + if (xdoc != null) + { + Version version = Assembly.GetExecutingAssembly().GetName().Version; + Version latest = Version.Parse(xdoc.Element("update").Attribute("latest").Value); + if (version < latest) + { + toolStripStatusLabel3.Tag = xdoc; + toolStripStatusLabel3.Text += $": {latest}"; + toolStripStatusLabel3.Visible = true; + } + } + } + + private void AddAccount(WalletAccount account, bool selected = false) + { + ListViewItem item = listView1.Items[account.Address]; + if (item != null) + { + if (!account.WatchOnly && ((WalletAccount)item.Tag).WatchOnly) + { + listView1.Items.Remove(item); + item = null; + } + } + if (item == null) + { + string groupName = account.WatchOnly ? "watchOnlyGroup" : IsSignatureContract(account.Contract.Script) ? "standardContractGroup" : "nonstandardContractGroup"; + item = listView1.Items.Add(new ListViewItem(new[] + { + new ListViewItem.ListViewSubItem + { + Name = "address", + Text = account.Address + }, + new ListViewItem.ListViewSubItem + { + Name = NativeContract.NEO.Symbol + }, + new ListViewItem.ListViewSubItem + { + Name = NativeContract.GAS.Symbol + } + }, -1, listView1.Groups[groupName]) + { + Name = account.Address, + Tag = account + }); + } + item.Selected = selected; + } + + private void Blockchain_PersistCompleted(Blockchain.PersistCompleted e) + { + if (IsDisposed) return; + persistence_time = DateTime.UtcNow; + if (Service.CurrentWallet != null) + check_nep5_balance = true; + BeginInvoke(new Action(RefreshConfirmations)); + } + + private static void OpenBrowser(string url) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Process.Start(new ProcessStartInfo("cmd", $"/c start {url}")); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + Process.Start("xdg-open", url); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + Process.Start("open", url); + } + } + + private void Service_WalletChanged(object sender, Wallet wallet) + { + if (InvokeRequired) + { + Invoke(new EventHandler(Service_WalletChanged), sender, wallet); + return; + } + + listView3.Items.Clear(); + 修改密码CToolStripMenuItem.Enabled = wallet != null; + 交易TToolStripMenuItem.Enabled = wallet != null; + signDataToolStripMenuItem.Enabled = wallet != null; + deployContractToolStripMenuItem.Enabled = wallet != null; + invokeContractToolStripMenuItem.Enabled = wallet != null; + 选举EToolStripMenuItem.Enabled = wallet != null; + 创建新地址NToolStripMenuItem.Enabled = wallet != null; + 导入私钥IToolStripMenuItem.Enabled = wallet != null; + 创建智能合约SToolStripMenuItem.Enabled = wallet != null; + listView1.Items.Clear(); + if (wallet != null) + { + foreach (WalletAccount account in wallet.GetAccounts().ToArray()) + { + AddAccount(account); + } + } + check_nep5_balance = true; + } + + private void RefreshConfirmations() + { + foreach (ListViewItem item in listView3.Items) + { + uint? height = item.Tag as uint?; + int? confirmations = (int)NativeContract.Ledger.CurrentIndex(Service.NeoSystem.StoreView) - (int?)height + 1; + if (confirmations <= 0) confirmations = null; + item.SubItems["confirmations"].Text = confirmations?.ToString() ?? Strings.Unconfirmed; + } + } + + private void MainForm_Load(object sender, EventArgs e) + { + actor = Service.NeoSystem.ActorSystem.ActorOf(EventWrapper.Props(Blockchain_PersistCompleted)); + Service.WalletChanged += Service_WalletChanged; + } + + private void MainForm_FormClosing(object sender, FormClosingEventArgs e) + { + if (actor != null) + Service.NeoSystem.ActorSystem.Stop(actor); + Service.WalletChanged -= Service_WalletChanged; + } + + private void timer1_Tick(object sender, EventArgs e) + { + uint height = NativeContract.Ledger.CurrentIndex(Service.NeoSystem.StoreView); + uint headerHeight = Service.NeoSystem.HeaderCache.Last?.Index ?? height; + + lbl_height.Text = $"{height}/{headerHeight}"; + lbl_count_node.Text = Service.LocalNode.ConnectedCount.ToString(); + TimeSpan persistence_span = DateTime.UtcNow - persistence_time; + if (persistence_span < TimeSpan.Zero) persistence_span = TimeSpan.Zero; + if (persistence_span > Service.NeoSystem.Settings.TimePerBlock) + { + toolStripProgressBar1.Style = ProgressBarStyle.Marquee; + } + else + { + toolStripProgressBar1.Value = persistence_span.Seconds; + toolStripProgressBar1.Style = ProgressBarStyle.Blocks; + } + if (Service.CurrentWallet is null) return; + if (!check_nep5_balance || persistence_span < TimeSpan.FromSeconds(2)) return; + check_nep5_balance = false; + UInt160[] addresses = Service.CurrentWallet.GetAccounts().Select(p => p.ScriptHash).ToArray(); + if (addresses.Length == 0) return; + using var snapshot = Service.NeoSystem.GetSnapshot(); + foreach (UInt160 assetId in NEP5Watched) + { + byte[] script; + using (ScriptBuilder sb = new ScriptBuilder()) + { + for (int i = addresses.Length - 1; i >= 0; i--) + sb.EmitDynamicCall(assetId, "balanceOf", addresses[i]); + sb.Emit(OpCode.DEPTH, OpCode.PACK); + sb.EmitDynamicCall(assetId, "decimals"); + sb.EmitDynamicCall(assetId, "name"); + script = sb.ToArray(); + } + using ApplicationEngine engine = ApplicationEngine.Run(script, snapshot, gas: 0_20000000L * addresses.Length); + if (engine.State.HasFlag(VMState.FAULT)) continue; + string name = engine.ResultStack.Pop().GetString(); + byte decimals = (byte)engine.ResultStack.Pop().GetInteger(); + BigInteger[] balances = ((VMArray)engine.ResultStack.Pop()).Select(p => p.GetInteger()).ToArray(); + string symbol = null; + if (assetId.Equals(NativeContract.NEO.Hash)) + symbol = NativeContract.NEO.Symbol; + else if (assetId.Equals(NativeContract.GAS.Hash)) + symbol = NativeContract.GAS.Symbol; + if (symbol != null) + for (int i = 0; i < addresses.Length; i++) + listView1.Items[addresses[i].ToAddress(Service.NeoSystem.Settings.AddressVersion)].SubItems[symbol].Text = new BigDecimal(balances[i], decimals).ToString(); + BigInteger amount = balances.Sum(); + if (amount == 0) + { + listView2.Items.RemoveByKey(assetId.ToString()); + continue; + } + BigDecimal balance = new BigDecimal(amount, decimals); + if (listView2.Items.ContainsKey(assetId.ToString())) + { + listView2.Items[assetId.ToString()].SubItems["value"].Text = balance.ToString(); + } + else + { + listView2.Items.Add(new ListViewItem(new[] + { + new ListViewItem.ListViewSubItem + { + Name = "name", + Text = name + }, + new ListViewItem.ListViewSubItem + { + Name = "type", + Text = "NEP-5" + }, + new ListViewItem.ListViewSubItem + { + Name = "value", + Text = balance.ToString() + }, + new ListViewItem.ListViewSubItem + { + ForeColor = Color.Gray, + Name = "issuer", + Text = $"ScriptHash:{assetId}" + } + }, -1) + { + Name = assetId.ToString(), + UseItemStyleForSubItems = false + }); + } + } + } + + private void 创建钱包数据库NToolStripMenuItem_Click(object sender, EventArgs e) + { + using CreateWalletDialog dialog = new CreateWalletDialog(); + if (dialog.ShowDialog() != DialogResult.OK) return; + Service.CreateWallet(dialog.WalletPath, dialog.Password); + } + + private void 打开钱包数据库OToolStripMenuItem_Click(object sender, EventArgs e) + { + using OpenWalletDialog dialog = new OpenWalletDialog(); + if (dialog.ShowDialog() != DialogResult.OK) return; + try + { + Service.OpenWallet(dialog.WalletPath, dialog.Password); + } + catch (CryptographicException) + { + MessageBox.Show(Strings.PasswordIncorrect); + } + } + + private void 修改密码CToolStripMenuItem_Click(object sender, EventArgs e) + { + using ChangePasswordDialog dialog = new ChangePasswordDialog(); + if (dialog.ShowDialog() != DialogResult.OK) return; + if (Service.CurrentWallet.ChangePassword(dialog.OldPassword, dialog.NewPassword)) + { + if (Service.CurrentWallet is NEP6Wallet wallet) + wallet.Save(); + MessageBox.Show(Strings.ChangePasswordSuccessful); + } + else + { + MessageBox.Show(Strings.PasswordIncorrect); + } + } + + private void 退出XToolStripMenuItem_Click(object sender, EventArgs e) + { + Close(); + } + + private void 转账TToolStripMenuItem_Click(object sender, EventArgs e) + { + Transaction tx; + using (TransferDialog dialog = new TransferDialog()) + { + if (dialog.ShowDialog() != DialogResult.OK) return; + tx = dialog.GetTransaction(); + } + using (InvokeContractDialog dialog = new InvokeContractDialog(tx)) + { + if (dialog.ShowDialog() != DialogResult.OK) return; + tx = dialog.GetTransaction(); + } + Helper.SignAndShowInformation(tx); + } + + private void 签名SToolStripMenuItem_Click(object sender, EventArgs e) + { + using SigningTxDialog dialog = new SigningTxDialog(); + dialog.ShowDialog(); + } + + private void deployContractToolStripMenuItem_Click(object sender, EventArgs e) + { + try + { + byte[] script; + using (DeployContractDialog dialog = new DeployContractDialog()) + { + if (dialog.ShowDialog() != DialogResult.OK) return; + script = dialog.GetScript(); + } + using (InvokeContractDialog dialog = new InvokeContractDialog(script)) + { + if (dialog.ShowDialog() != DialogResult.OK) return; + Helper.SignAndShowInformation(dialog.GetTransaction()); + } + } + catch { } + } + + private void invokeContractToolStripMenuItem_Click(object sender, EventArgs e) + { + using InvokeContractDialog dialog = new InvokeContractDialog(); + if (dialog.ShowDialog() != DialogResult.OK) return; + try + { + Helper.SignAndShowInformation(dialog.GetTransaction()); + } + catch + { + return; + } + } + + private void 选举EToolStripMenuItem_Click(object sender, EventArgs e) + { + try + { + byte[] script; + using (ElectionDialog dialog = new ElectionDialog()) + { + if (dialog.ShowDialog() != DialogResult.OK) return; + script = dialog.GetScript(); + } + using (InvokeContractDialog dialog = new InvokeContractDialog(script)) + { + if (dialog.ShowDialog() != DialogResult.OK) return; + Helper.SignAndShowInformation(dialog.GetTransaction()); + } + } + catch { } + } + + private void signDataToolStripMenuItem_Click(object sender, EventArgs e) + { + using SigningDialog dialog = new SigningDialog(); + dialog.ShowDialog(); + } + + private void optionsToolStripMenuItem_Click(object sender, EventArgs e) + { + } + + private void 官网WToolStripMenuItem_Click(object sender, EventArgs e) + { + OpenBrowser("https://neo.org/"); + } + + private void 开发人员工具TToolStripMenuItem_Click(object sender, EventArgs e) + { + Helper.Show(); + } + + private void consoleToolStripMenuItem_Click(object sender, EventArgs e) + { + Helper.Show(); + } + + private void 关于AntSharesToolStripMenuItem_Click(object sender, EventArgs e) + { + MessageBox.Show($"{Strings.AboutMessage} {Strings.AboutVersion}{Assembly.GetExecutingAssembly().GetName().Version}", Strings.About); + } + + private void contextMenuStrip1_Opening(object sender, CancelEventArgs e) + { + 查看私钥VToolStripMenuItem.Enabled = + listView1.SelectedIndices.Count == 1 && + !((WalletAccount)listView1.SelectedItems[0].Tag).WatchOnly && + IsSignatureContract(((WalletAccount)listView1.SelectedItems[0].Tag).Contract.Script); + viewContractToolStripMenuItem.Enabled = + listView1.SelectedIndices.Count == 1 && + !((WalletAccount)listView1.SelectedItems[0].Tag).WatchOnly; + voteToolStripMenuItem.Enabled = + listView1.SelectedIndices.Count == 1 && + !((WalletAccount)listView1.SelectedItems[0].Tag).WatchOnly && + !string.IsNullOrEmpty(listView1.SelectedItems[0].SubItems[NativeContract.NEO.Symbol].Text) && + decimal.Parse(listView1.SelectedItems[0].SubItems[NativeContract.NEO.Symbol].Text) > 0; + 复制到剪贴板CToolStripMenuItem.Enabled = listView1.SelectedIndices.Count == 1; + 删除DToolStripMenuItem.Enabled = listView1.SelectedIndices.Count > 0; + } + + private void 创建新地址NToolStripMenuItem_Click(object sender, EventArgs e) + { + listView1.SelectedIndices.Clear(); + WalletAccount account = Service.CurrentWallet.CreateAccount(); + AddAccount(account, true); + if (Service.CurrentWallet is NEP6Wallet wallet) + wallet.Save(); + } + + private void importWIFToolStripMenuItem_Click(object sender, EventArgs e) + { + using ImportPrivateKeyDialog dialog = new ImportPrivateKeyDialog(); + if (dialog.ShowDialog() != DialogResult.OK) return; + listView1.SelectedIndices.Clear(); + foreach (string wif in dialog.WifStrings) + { + WalletAccount account; + try + { + account = Service.CurrentWallet.Import(wif); + } + catch (FormatException) + { + continue; + } + AddAccount(account, true); + } + if (Service.CurrentWallet is NEP6Wallet wallet) + wallet.Save(); + } + + private void importWatchOnlyAddressToolStripMenuItem_Click(object sender, EventArgs e) + { + string text = InputBox.Show(Strings.Address, Strings.ImportWatchOnlyAddress); + if (string.IsNullOrEmpty(text)) return; + using (StringReader reader = new StringReader(text)) + { + while (true) + { + string address = reader.ReadLine(); + if (address == null) break; + address = address.Trim(); + if (string.IsNullOrEmpty(address)) continue; + UInt160 scriptHash; + try + { + scriptHash = address.ToScriptHash(Service.NeoSystem.Settings.AddressVersion); + } + catch (FormatException) + { + continue; + } + WalletAccount account = Service.CurrentWallet.CreateAccount(scriptHash); + AddAccount(account, true); + } + } + if (Service.CurrentWallet is NEP6Wallet wallet) + wallet.Save(); + } + + private void 多方签名MToolStripMenuItem_Click(object sender, EventArgs e) + { + using CreateMultiSigContractDialog dialog = new CreateMultiSigContractDialog(); + if (dialog.ShowDialog() != DialogResult.OK) return; + Contract contract = dialog.GetContract(); + if (contract == null) + { + MessageBox.Show(Strings.AddContractFailedMessage); + return; + } + WalletAccount account = Service.CurrentWallet.CreateAccount(contract, dialog.GetKey()); + if (Service.CurrentWallet is NEP6Wallet wallet) + wallet.Save(); + listView1.SelectedIndices.Clear(); + AddAccount(account, true); + } + + private void 自定义CToolStripMenuItem_Click(object sender, EventArgs e) + { + using ImportCustomContractDialog dialog = new ImportCustomContractDialog(); + if (dialog.ShowDialog() != DialogResult.OK) return; + Contract contract = dialog.GetContract(); + WalletAccount account = Service.CurrentWallet.CreateAccount(contract, dialog.GetKey()); + if (Service.CurrentWallet is NEP6Wallet wallet) + wallet.Save(); + listView1.SelectedIndices.Clear(); + AddAccount(account, true); + } + + private void 查看私钥VToolStripMenuItem_Click(object sender, EventArgs e) + { + WalletAccount account = (WalletAccount)listView1.SelectedItems[0].Tag; + using ViewPrivateKeyDialog dialog = new ViewPrivateKeyDialog(account); + dialog.ShowDialog(); + } + + private void viewContractToolStripMenuItem_Click(object sender, EventArgs e) + { + WalletAccount account = (WalletAccount)listView1.SelectedItems[0].Tag; + using ViewContractDialog dialog = new ViewContractDialog(account.Contract); + dialog.ShowDialog(); + } + + private void voteToolStripMenuItem_Click(object sender, EventArgs e) + { + try + { + WalletAccount account = (WalletAccount)listView1.SelectedItems[0].Tag; + byte[] script; + using (VotingDialog dialog = new VotingDialog(account.ScriptHash)) + { + if (dialog.ShowDialog() != DialogResult.OK) return; + script = dialog.GetScript(); + } + using (InvokeContractDialog dialog = new InvokeContractDialog(script)) + { + if (dialog.ShowDialog() != DialogResult.OK) return; + Helper.SignAndShowInformation(dialog.GetTransaction()); + } + } + catch { } + } + + private void 复制到剪贴板CToolStripMenuItem_Click(object sender, EventArgs e) + { + try + { + Clipboard.SetText(listView1.SelectedItems[0].Text); + } + catch (ExternalException) { } + } + + private void 删除DToolStripMenuItem_Click(object sender, EventArgs e) + { + if (MessageBox.Show(Strings.DeleteAddressConfirmationMessage, Strings.DeleteAddressConfirmationCaption, MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) != DialogResult.Yes) + return; + WalletAccount[] accounts = listView1.SelectedItems.OfType().Select(p => (WalletAccount)p.Tag).ToArray(); + foreach (WalletAccount account in accounts) + { + listView1.Items.RemoveByKey(account.Address); + Service.CurrentWallet.DeleteAccount(account.ScriptHash); + } + if (Service.CurrentWallet is NEP6Wallet wallet) + wallet.Save(); + check_nep5_balance = true; + } + + private void toolStripMenuItem1_Click(object sender, EventArgs e) + { + if (listView3.SelectedItems.Count == 0) return; + Clipboard.SetDataObject(listView3.SelectedItems[0].SubItems[1].Text); + } + + private void listView1_DoubleClick(object sender, EventArgs e) + { + if (listView1.SelectedIndices.Count == 0) return; + OpenBrowser($"https://neoscan.io/address/{listView1.SelectedItems[0].Text}"); + } + + private void listView2_DoubleClick(object sender, EventArgs e) + { + if (listView2.SelectedIndices.Count == 0) return; + OpenBrowser($"https://neoscan.io/asset/{listView2.SelectedItems[0].Name[2..]}"); + } + + private void listView3_DoubleClick(object sender, EventArgs e) + { + if (listView3.SelectedIndices.Count == 0) return; + OpenBrowser($"https://neoscan.io/transaction/{listView3.SelectedItems[0].Name[2..]}"); + } + + private void toolStripStatusLabel3_Click(object sender, EventArgs e) + { + using UpdateDialog dialog = new UpdateDialog((XDocument)toolStripStatusLabel3.Tag); + dialog.ShowDialog(); + } + } +} diff --git a/src/Neo.GUI/GUI/MainForm.es-ES.resx b/src/Neo.GUI/GUI/MainForm.es-ES.resx new file mode 100644 index 0000000000..468113ceba --- /dev/null +++ b/src/Neo.GUI/GUI/MainForm.es-ES.resx @@ -0,0 +1,437 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + 210, 22 + + + &Nueva base de datos... + + + 210, 22 + + + &Abrir base de datos... + + + 207, 6 + + + 210, 22 + + + &Cambiar contraseña... + + + 207, 6 + + + 210, 22 + + + &Salir + + + 82, 21 + + + &Monedero + + + 141, 22 + + + &Transferir... + + + 138, 6 + + + 141, 22 + + + &Firma... + + + 89, 21 + + + &Transacción + + + 198, 22 + + + &Desplegar contrato... + + + 198, 22 + + + I&nvocar contrato... + + + 195, 6 + + + 198, 22 + + + &Votación... + + + 198, 22 + + + 195, 6 + + + 198, 22 + + + &Opciones... + + + A&vanzado + + + 259, 22 + + + &Obtener ayuda + + + 259, 22 + + + &Web oficial + + + 256, 6 + + + 259, 22 + + + &Herramienta de desarrollo + + + 259, 22 + + + 256, 6 + + + 259, 22 + + + &Acerca de NEO + + + 56, 21 + + + &Ayuda + + + Dirección + + + 275, 22 + + + Crear &nueva dirección + + + 266, 22 + + + Importar desde &WIF... + + + 263, 6 + + + 266, 22 + + + Importar dirección sólo lectur&a... + + + 275, 22 + + + &Importar + + + 178, 22 + + + &Múltiples firmas... + + + 175, 6 + + + 178, 22 + + + &Personalizado... + + + 275, 22 + + + Crear nueva &dirección de contrato + + + 272, 6 + + + 275, 22 + + + Ver clave &privada + + + 275, 22 + + + Ver c&ontrato + + + 275, 22 + + + &Votar... + + + 275, 22 + + + &Copiar al portapapeles + + + 275, 22 + + + &Eliminar... + + + 276, 186 + + + + AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w + LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACJTeXN0 + ZW0uV2luZG93cy5Gb3Jtcy5MaXN0Vmlld0dyb3VwBAAAAAZIZWFkZXIPSGVhZGVyQWxpZ25tZW50A1Rh + ZwROYW1lAQQCAShTeXN0ZW0uV2luZG93cy5Gb3Jtcy5Ib3Jpem9udGFsQWxpZ25tZW50AgAAAAIAAAAG + AwAAABBDdWVudGEgZXN0w6FuZGFyBfz///8oU3lzdGVtLldpbmRvd3MuRm9ybXMuSG9yaXpvbnRhbEFs + aWdubWVudAEAAAAHdmFsdWVfXwAIAgAAAAAAAAAKBgUAAAAVc3RhbmRhcmRDb250cmFjdEdyb3VwCw== + + + + + AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w + LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACJTeXN0 + ZW0uV2luZG93cy5Gb3Jtcy5MaXN0Vmlld0dyb3VwBAAAAAZIZWFkZXIPSGVhZGVyQWxpZ25tZW50A1Rh + ZwROYW1lAQQCAShTeXN0ZW0uV2luZG93cy5Gb3Jtcy5Ib3Jpem9udGFsQWxpZ25tZW50AgAAAAIAAAAG + AwAAABZEaXJlY2Npw7NuIGRlIGNvbnRyYXRvBfz///8oU3lzdGVtLldpbmRvd3MuRm9ybXMuSG9yaXpv + bnRhbEFsaWdubWVudAEAAAAHdmFsdWVfXwAIAgAAAAAAAAAKBgUAAAAYbm9uc3RhbmRhcmRDb250cmFj + dEdyb3VwCw== + + + + + AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w + LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACJTeXN0 + ZW0uV2luZG93cy5Gb3Jtcy5MaXN0Vmlld0dyb3VwBAAAAAZIZWFkZXIPSGVhZGVyQWxpZ25tZW50A1Rh + ZwROYW1lAQQCAShTeXN0ZW0uV2luZG93cy5Gb3Jtcy5Ib3Jpem9udGFsQWxpZ25tZW50AgAAAAIAAAAG + AwAAABhEaXJlY2Npw7NuIHPDs2xvIGxlY3R1cmEF/P///yhTeXN0ZW0uV2luZG93cy5Gb3Jtcy5Ib3Jp + em9udGFsQWxpZ25tZW50AQAAAAd2YWx1ZV9fAAgCAAAAAAAAAAoGBQAAAA53YXRjaE9ubHlHcm91cAs= + + + + 58, 17 + + + Tamaño: + + + 74, 17 + + + Conectado: + + + 172, 17 + + + Esperando próximo bloque: + + + 152, 17 + + + Descargar nueva versión + + + Cuenta + + + Activo + + + Tipo + + + Saldo + + + Emisor + + + Activos + + + Fecha + + + ID de la transacción + + + Confirmación + + + Tipo + + + 147, 22 + + + &Copiar TXID + + + 148, 26 + + + Historial de transacciones + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/MainForm.resx b/src/Neo.GUI/GUI/MainForm.resx new file mode 100644 index 0000000000..21c7f5a2b2 --- /dev/null +++ b/src/Neo.GUI/GUI/MainForm.resx @@ -0,0 +1,1488 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + + 216, 22 + + + &New Wallet Database... + + + 216, 22 + + + &Open Wallet Database... + + + 213, 6 + + + + False + + + 216, 22 + + + &Change Password... + + + 213, 6 + + + 216, 22 + + + E&xit + + + 56, 21 + + + &Wallet + + + 140, 22 + + + &Transfer... + + + 137, 6 + + + 140, 22 + + + &Signature... + + + False + + + 87, 21 + + + &Transaction + + + False + + + 179, 22 + + + &Deploy Contract... + + + False + + + 179, 22 + + + In&voke Contract... + + + 176, 6 + + + False + + + 179, 22 + + + &Election... + + + False + + + 179, 22 + + + &Sign Message... + + + 176, 6 + + + 179, 22 + + + &Options... + + + 77, 21 + + + &Advanced + + + 194, 22 + + + Check for &Help + + + 194, 22 + + + Official &Web + + + 191, 6 + + + + F12 + + + 194, 22 + + + Developer &Tool + + + 194, 22 + + + &Console + + + 191, 6 + + + 194, 22 + + + &About NEO + + + 47, 21 + + + &Help + + + 0, 0 + + + 7, 3, 0, 3 + + + 903, 27 + + + 0 + + + menuStrip1 + + + menuStrip1 + + + System.Windows.Forms.MenuStrip, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 4 + + + Address + + + 300 + + + NEO + + + 120 + + + GAS + + + 120 + + + 348, 17 + + + False + + + 198, 22 + + + Create &New Add. + + + 248, 22 + + + Import from &WIF... + + + 245, 6 + + + 248, 22 + + + Import Watch-Only &Address... + + + False + + + 198, 22 + + + &Import + + + 174, 22 + + + &Multi-Signature... + + + 171, 6 + + + 174, 22 + + + &Custom... + + + False + + + 198, 22 + + + Create Contract &Add. + + + 195, 6 + + + False + + + 198, 22 + + + View &Private Key + + + False + + + 198, 22 + + + View C&ontract + + + False + + + 198, 22 + + + &Vote... + + + False + + + Ctrl+C + + + False + + + 198, 22 + + + &Copy to Clipboard + + + False + + + 198, 22 + + + &Delete... + + + 199, 186 + + + contextMenuStrip1 + + + System.Windows.Forms.ContextMenuStrip, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Fill + + + + AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w + LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACJTeXN0 + ZW0uV2luZG93cy5Gb3Jtcy5MaXN0Vmlld0dyb3VwBAAAAAZIZWFkZXIPSGVhZGVyQWxpZ25tZW50A1Rh + ZwROYW1lAQQCAShTeXN0ZW0uV2luZG93cy5Gb3Jtcy5Ib3Jpem9udGFsQWxpZ25tZW50AgAAAAIAAAAG + AwAAABBTdGFuZGFyZCBBY2NvdW50Bfz///8oU3lzdGVtLldpbmRvd3MuRm9ybXMuSG9yaXpvbnRhbEFs + aWdubWVudAEAAAAHdmFsdWVfXwAIAgAAAAAAAAAKBgUAAAAVc3RhbmRhcmRDb250cmFjdEdyb3VwCw== + + + + + AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w + LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACJTeXN0 + ZW0uV2luZG93cy5Gb3Jtcy5MaXN0Vmlld0dyb3VwBAAAAAZIZWFkZXIPSGVhZGVyQWxpZ25tZW50A1Rh + ZwROYW1lAQQCAShTeXN0ZW0uV2luZG93cy5Gb3Jtcy5Ib3Jpem9udGFsQWxpZ25tZW50AgAAAAIAAAAG + AwAAABBDb250cmFjdCBBZGRyZXNzBfz///8oU3lzdGVtLldpbmRvd3MuRm9ybXMuSG9yaXpvbnRhbEFs + aWdubWVudAEAAAAHdmFsdWVfXwAIAgAAAAAAAAAKBgUAAAAYbm9uc3RhbmRhcmRDb250cmFjdEdyb3Vw + Cw== + + + + + AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w + LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACJTeXN0 + ZW0uV2luZG93cy5Gb3Jtcy5MaXN0Vmlld0dyb3VwBAAAAAZIZWFkZXIPSGVhZGVyQWxpZ25tZW50A1Rh + ZwROYW1lAQQCAShTeXN0ZW0uV2luZG93cy5Gb3Jtcy5Ib3Jpem9udGFsQWxpZ25tZW50AgAAAAIAAAAG + AwAAABJXYXRjaC1Pbmx5IEFkZHJlc3MF/P///yhTeXN0ZW0uV2luZG93cy5Gb3Jtcy5Ib3Jpem9udGFs + QWxpZ25tZW50AQAAAAd2YWx1ZV9fAAgCAAAAAAAAAAoGBQAAAA53YXRjaE9ubHlHcm91cAs= + + + + 3, 3 + + + 889, 521 + + + 1 + + + listView1 + + + System.Windows.Forms.ListView, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tabPage1 + + + 0 + + + 137, 17 + + + 49, 17 + + + Height: + + + 27, 17 + + + 0/0 + + + 73, 17 + + + Connected: + + + 15, 17 + + + 0 + + + 100, 16 + + + 140, 17 + + + Waiting for next block: + + + 145, 17 + + + Download New Version + + + False + + + 0, 584 + + + 903, 22 + + + 2 + + + statusStrip1 + + + statusStrip1 + + + System.Windows.Forms.StatusStrip, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 3 + + + 258, 17 + + + 4, 26 + + + 3, 3, 3, 3 + + + 895, 527 + + + 0 + + + Account + + + tabPage1 + + + System.Windows.Forms.TabPage, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tabControl1 + + + 0 + + + Asset + + + 160 + + + Type + + + 100 + + + Balance + + + 192 + + + Issuer + + + 398 + + + Fill + + + 3, 3 + + + 889, 521 + + + 2 + + + listView2 + + + System.Windows.Forms.ListView, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tabPage2 + + + 0 + + + 4, 26 + + + 3, 3, 3, 3 + + + 895, 527 + + + 1 + + + Asset + + + tabPage2 + + + System.Windows.Forms.TabPage, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tabControl1 + + + 1 + + + Time + + + 132 + + + Transaction ID + + + 482 + + + confirm + + + 78 + + + Transaction Type + + + 163 + + + 513, 17 + + + 138, 22 + + + &Copy TXID + + + 139, 26 + + + contextMenuStrip3 + + + System.Windows.Forms.ContextMenuStrip, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Fill + + + 3, 3 + + + 889, 521 + + + 0 + + + listView3 + + + System.Windows.Forms.ListView, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tabPage3 + + + 0 + + + 4, 26 + + + 3, 3, 3, 3 + + + 895, 527 + + + 2 + + + Transaction History + + + tabPage3 + + + System.Windows.Forms.TabPage, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + tabControl1 + + + 2 + + + Fill + + + 0, 27 + + + 903, 557 + + + 3 + + + tabControl1 + + + System.Windows.Forms.TabControl, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 2 + + + True + + + 7, 17 + + + 903, 606 + + + Microsoft YaHei UI, 9pt + + + + AAABAAEAQEAAAAEAIAAoQgAAFgAAACgAAABAAAAAgAAAAAEAIAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAA2G8OANdrdgDWaJMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAADadhYA2XOFANhv7wDXav8A1WarAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAANx+HgDbepMA2nb1ANhx/wDXbP8A1mf/ANVjqwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3oUoAN2BoQDcffsA23j/ANlz/wDYbv8A1mn/ANVk/wDU + YKsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgjTYA34mvAN6E/QDcf/8A23r/ANp1/wDY + cP8A12v/ANZm/wDUYf8A012rAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5JgAAOKUQgDhkL0A4Iz/AN+H/wDd + gv8A3H3/ANp4/wDZc/8A2G7/ANZo/wDVZP8A017/ANJaqwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADmnwIA5JtQAOOXyQDi + k/8A4Y7/AN+J/wDehP8A3H//ANt6/wDadf8A2HD/ANdr/wDVZv8A1GH/ANNc/wDRV6sAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOinBADm + o14A5Z/XAOSa/wDjlf8A4ZD/AOCL/wDehv8A3YH/ANx8/wDad/8A2XL/ANdt/wDWaP8A1WP/ANNe/wDS + Wf8A0VWrAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAOipRgDnpuEA5qH/AOWc/wDjl/8A4pL/AOCN/wDfiP8A3oP/ANx+/wDbef8A2XT/ANhv/wDX + av8A1WX/ANRg/wDSW/8A0Vb/ANBSqwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADop34A56P/AOWe/wDkmf8A4pT/AOGP/wDgiv8A3oX/AN2A/wDb + e/8A2nb/ANlx/wDXbP8A1mf/ANRi/wDTXf8A0lj/ANBT/wDPT6sAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAL0NMAC9DXIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA56R+AOah/wDknP8A45f/AOKS/wDg + jf8A34j/AN2D/wDcff8A23n/ANlz/wDYb/8A1mn/ANVk/wDUX/8A0lr/ANFV/wDPUP8AzkyrAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvQ08AL0NtwC9Df8AvQ2/AAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOaifgDl + nv8A5Jn/AOKU/wDhj/8A34r/AN6F/wDdgP8A23v/ANp2/wDYcf8A12z/ANZn/wDUYv8A013/ANFY/wDQ + U/8Az07/AM1JqwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL0NAAC9DUoAvQ3FAL0N/wC9Df8AvQ3/AL0NvwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAADln34A5Jv/AOOW/wDhkf8A4Iz/AN+H/wDdgv8A3H3/ANp4/wDZc/8A2G7/ANZp/wDV + ZP8A01//ANJa/wDRVf8Az1D/AM5L/wDNR6sAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvQ0EAL0NWAC9DdEAvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Db8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5Jx+AOOY/wDik/8A4Y7/AN+J/wDehP8A3H//ANt6/wDa + df8A2HD/ANdr/wDVZv8A1GH/ANNc/wDRV/8A0FL/AM5N/wDNSP8AzESrAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC9DQgAvQ1mAL0N3QC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ2/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOSZfgDjlf8A4ZD/AOCL/wDe + hv8A3YH/ANx8/wDad/8A2XL/ANdt/wDWaP8A1WP/ANNe/wDSWf8A0FT/AM9P/wDOSv8AzEX/AMtBqwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL0NDgC9 + DXQAvQ3nAL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0NvwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADj + ln4A4pL/AOCO/wDfiP8A3oT/ANx+/wDbef8A2XT/ANhv/wDXav8A1WX/ANRg/wDSW/8A0Vb/ANBR/wDO + TP8AzUf/AMxC/wDKPqsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAvQ0UAL0NgwC9De8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Db8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAA4pN+AOGQ/wDgi/8A3ob/AN2B/wDbfP8A2nf/ANly/wDXbf8A1mj/ANRj/wDT + Xv8A0ln/ANBU/wDPT/8AzUn/AMxF/wDLP/8AyjurAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAC9DR4AvQ2RAL0N9QC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ2/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOGRfgDgjf8A34j/AN2D/wDcfv8A23n/ANl0/wDY + b/8A12r/ANVl/wDUYP8A0lv/ANFW/wDQUf8Azkz/AM1H/wDLQv8Ayj3/AMk4qwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAL0NKAC9DZ8AvQ35AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0NvwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADhjn4A4Ir/AN6F/wDd + gP8A23v/ANp2/wDZcf8A12z/ANZn/wDUYv8A013/ANJY/wDQU/8Az07/AM1J/wDMRP8Ayz//AMk6/wDI + NqsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC9DdsAvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Db8AAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAA4It+AN+H/wDdgv8A3H3/ANt4/wDZc/8A2G7/ANZp/wDVZP8A1F//ANJa/wDRVf8Az1D/AM5L/wDN + Rv8Ay0H/AMo8/wDIN/8AxzOrAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvQ3bAL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ2/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAN+IfgDehP8A3X//ANt6/wDadf8A2HD/ANdr/wDWZv8A1GH/ANNc/wDR + V/8A0FL/AM9N/wDNSP8AzEP/AMo+/wDJOf8AyDT/AMYwqwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAL0N2wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0NvwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADehX4A3YL/ANx9/wDaeP8A2XP/ANhu/wDW + af8A1WT/ANNe/wDSWv8A0VT/AM9Q/wDOSv8AzEX/AMtA/wDKO/8AyDb/AMcx/wDGLasAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC9DdsAvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Db8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3YN+ANx//wDb + ev8A2nX/ANhw/wDXa/8A1Wb/ANRh/wDTXP8A0Vf/ANBS/wDOTf8AzUj/AMxD/wDKPv8AyTn/AMc0/wDG + L/8AxSqrAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvQ3bAL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ2/AAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAN2AfgDcfP8A2nf/ANly/wDXbf8A1mj/ANVj/wDTXv8A0ln/ANBU/wDPT/8Azkr/AMxF/wDL + QP8AyTv/AMg2/wDHMf8AxSz/AMQoqwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL0N2wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0NvwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADcfX4A23n/ANl0/wDYb/8A12r/ANVl/wDUYP8A0lv/ANFW/wDQ + Uf8Azkz/AM1H/wDLQv8Ayj3/AMk4/wDHM/8Axi7/AMQp/wDDJasAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAC9DdsAvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Db8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA23p+ANp2/wDZcf8A12z/ANZn/wDU + Yv8A013/ANJY/wDQU/8Az07/AM1J/wDMRP8Ayz//AMk6/wDINf8AxjD/AMUr/wDEJv8AwiKrAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvQ3bAL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ2/AAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANp3fgDZ + c/8A2G//ANZp/wDVZf8A1F//ANJa/wDRVf8Az1D/AM5L/wDNRv8Ay0H/AMo8/wDIN/8AxzL/AMYt/wDE + KP8AwyP/AMIfqwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL0N2wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0NvwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAADZdH4A2HH/ANds/wDWZ/8A1GL/ANNd/wDRWP8A0FP/AM9O/wDNSf8AzET/AMo//wDJ + Ov8AyDX/AMYw/wDFKv8Awyb/AMIg/wDBHKsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC9 + DdsAvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Db8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2XJ+ANhu/wDWaf8A1WT/ANNf/wDSWv8A0VX/AM9Q/wDO + S/8AzEb/AMtB/wDKPP8AyDf/AMcy/wDFLf8AxCj/AMMj/wDBHv8AwBmrAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAvQ3bAL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ2/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANhvfgDXa/8A1Wb/ANRh/wDT + XP8A0Vf/ANBS/wDOTf8AzUj/AMxD/wDKPv8AyTn/AMg0/wDGL/8AxSr/AMMl/wDCIP8AwRv/AL8XqwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL0N2wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0NvwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADX + bH4A1mj/ANVj/wDTXv8A0ln/ANBU/wDPT/8Azkr/AMxF/wDLQP8AyTv/AMg2/wDHMf8AxSz/AMQn/wDD + Iv8AwR3/AMAY/wC/FKsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC9DdsAvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Db8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAA1ml+ANVl/wDUYP8A0lv/ANFW/wDQUf8Azkz/AM1H/wDMQv8Ayj3/AMk4/wDH + M/8Axi7/AMUp/wDDJP8Awh//AMAa/wC/Ff8AvhGrAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAvQ3bAL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ2/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANZmfgDVY/8A017/ANJZ/wDQVP8Az0//AM5K/wDM + Rf8Ayz//AMk7/wDINf8AxzH/AMUr/wDEJv8AwiH/AMEc/wDAF/8AvhL/AL0OqwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAL0N2wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0NvwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADVZH4A1GD/ANJb/wDR + Vv8A0FH/AM5M/wDNR/8Ay0L/AMo9/wDJOP8AxzP/AMYu/wDEKf8AwyT/AMIf/wDAGv8AvxX/AL0Q/wC9 + DasAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC9DdsAvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9DL8AAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAA1GF+ANNd/wDSWP8A0FP/AM9O/wDNSf8AzET/AMs//wDJOv8AyDX/AMYw/wDFK/8AxCb/AMIh/wDB + HP8Avxf/AL4S/wC9Df8AvQ2rAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvQ3bAL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + DP8AvQy/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAANNefgDSWv8A0VX/AM9Q/wDOS/8AzUb/AMtB/wDKPP8AyDf/AMcy/wDG + Lf8AxCj/AMMj/wDBHv8AwBn/AL8U/wC9D/8AvQ3VAL0NTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAL0N2wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQz/ALwMvwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADSW34A0Vf/ANBS/wDPTf8AzUj/AMxD/wDK + Pv8AyTn/AMg0/wDGL/8AxSr/AMMl/wDCIP8AwRv/AL8W/wC+EskAvQ5QAL0NMgC9DakAvQ3RAL0NdgC9 + DRwAAAAAAAAAAAAAAAAAAAAAAAAAAAC9DdsAvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQz/ALwM/wC8C78AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0lh+ANFU/wDP + UP8Azkr/AMxG/wDLQP8Ayjv/AMg2/wDHMf8AxSz/AMQn/wDDIv8AwR3/AMAZuwC/FUIAvQ0+AL0NtwC9 + Df8AvQ3/AL0N/wC9Df8AvQ39AL0NvQC9DWIAvQ0OAAAAAAAAAAAAvQ3bAL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQz/AL0M/wC8C/8AvAu/AAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAANFVfgDQUv8Azk3/AM1I/wDMQ/8Ayj7/AMk5/wDHNP8Axi//AMUq/wDDJf0AwiCtAMEcNgC/ + FEwAvhDFAL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N9wC9DakAvQ1OAL0NJgC9 + DXwAvQ3XAL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0M/wC8 + DP8AvAv/ALwLvwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQU34Az0//AM5K/wDMRf8Ay0D/AMk7/wDINv8AxzH/AMUs+QDE + J58AwyMsAMEbWAC/F9EAvhP/AL0O/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9DesAvQ2VAL0NOAC9DTQAvQ2RAL0N6QC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0M/wC9DP8AvAv/ALwL/wC8C78AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAz1B+AM5M/wDNR/8Ay0L/AMo9/wDJ + OP8AxzP1AMYvkwDFKiYAwyNmAMIf3QDAGv8AvxX/AL0Q/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3bAL0NgQC9DSgAvQ1IAL0NpQC9 + DfUAvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9DP8AvAz/ALwL/wC8C/8AvAq/AAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM5NfgDN + Sf8AzET/AMs//wDJOu8AyDaFAMcxIgDFKnQAxCbnAMIh/wDBHP8Avxf/AL4S/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0NyQC9DWwAvQ0gAL0NXAC9DbkAvQ37AL0N/wC9DP8AvAz/ALwL/wC8C/8AvAr/ALwKvwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAADOSn4AzUb/AMtC5wDKPXYAyDciAMcxgwDGLe8AxCj/AMMj/wDBHv8AwBn/AL8U/wC9 + D/8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N+wC9DbMAvQ1YAL0NIgC9DXAAvQzNALwM/wC8 + C/8AvAv/ALwK/wC7Cr8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzUhgAMxFaADKPSYAyTmRAMg09QDGMP8AxSv/AMMm/wDC + IP8AwRz/AL8W/wC+Ev8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + DfEAvQyfALwMRAC8CywAvAuHALwK4QC8Cv8Auwm/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADLQEQAyjztAMg3/wDH + Mv8Axi3/AMQo/wDDI/8AwR7/AMAZ/wC/FP8AvQ//AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0M/wC8DP8AvAvlALwLiwC8CjAAuwo+ALsJagAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAMk5CgDINFoAxi+3AMUq+wDDJf8AwiD/AMEb/wC/Fv8AvhH/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0M/wC8DP8AvAv/ALwL/wC8Cv8AvAr/ALsJxQC7 + CRoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADEJxYAwyJuAMEdywDAGP8AvhP/AL0O/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0M/wC9DP8AvAv/ALwL/wC8 + Cv8AvArpALsKeAC7CRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAvxUoAL4RgwC9Dd8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + DP8AvAz/ALwL/wC8C98AvApqALwKCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvQ0AAL0NPAC9DZcAvQ3tAL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9DP8AvAz/ALwL1QC8C1wAvAsEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL0NBgC9 + DVAAvQ2rAL0N9wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQzJALwMUAC8DAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC9DRAAvQ1kAL0NwQC9Df0AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9 + Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9DbsAvQ1CAL0MAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAvQ0eAL0NeAC9 + DdUAvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ3/AL0N/wC9Df8AvQ39AL0NrQC9DTQAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAL0NMAC9DY0AvQ3lAL0N/wC9Df8AvQ3/AL0N/wC9DfkAvQ2fAL0NKAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL0NAgC9DUYAvQ2hAL0N6QC9 + DZEAvQ0eAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA/////////////////////////////////////////////////////////+//////////D/// + //////wP////////8A/////////AD////////wAP///////8AA////////AAD///////wAAP///////A + AA///////8AAD///8f//wAAP///B///AAA///wH//8AAD//8Af//wAAP//AB///AAA//gAH//8AAD/4A + Af//wAAP+AAB///AAA/wAAH//8AAD/AAAf//wAAP8AAB///AAA/wAAH//8AAD/AAAf//wAAP8AAB///A + AA/wAAH//8AAD/AAAf//wAAP8AAB///AAA/wAAH//8AAD/AAAf//wAAP8AAB///AAA/wAAH//8AAD/AA + Af//wAAP8AAB///AAA/wAAH//8AAD/AAAf//wAAf8AAB///AAGfwAAH//8ABgPAAAf//wAYAHAAB///A + GAADAAH//8BgAABgAf//wYAAABwB///MAAAAA4H///AAAAAAYf//4AAAAAAP///4AAAAAAP///8AAAAA + D////8AAAAA/////+AAAAP//////AAAD///////gAA////////wAP////////wD/////////4/////// + //////////////////////////////////////////////////8= + + + + 3, 4, 3, 4 + + + CenterScreen + + + neo-gui + + + 钱包WToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 创建钱包数据库NToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 打开钱包数据库OToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + toolStripSeparator1 + + + System.Windows.Forms.ToolStripSeparator, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 修改密码CToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + toolStripSeparator2 + + + System.Windows.Forms.ToolStripSeparator, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 退出XToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 交易TToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 转账TToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + toolStripSeparator5 + + + System.Windows.Forms.ToolStripSeparator, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 签名SToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 高级AToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + deployContractToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + invokeContractToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + toolStripSeparator11 + + + System.Windows.Forms.ToolStripSeparator, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 选举EToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + signDataToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + toolStripSeparator9 + + + System.Windows.Forms.ToolStripSeparator, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + optionsToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 帮助HToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 查看帮助VToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 官网WToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + toolStripSeparator3 + + + System.Windows.Forms.ToolStripSeparator, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 开发人员工具TToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + consoleToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + toolStripSeparator4 + + + System.Windows.Forms.ToolStripSeparator, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 关于AntSharesToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + columnHeader1 + + + System.Windows.Forms.ColumnHeader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + columnHeader4 + + + System.Windows.Forms.ColumnHeader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + columnHeader11 + + + System.Windows.Forms.ColumnHeader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 创建新地址NToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 导入私钥IToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + importWIFToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + toolStripSeparator10 + + + System.Windows.Forms.ToolStripSeparator, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + importWatchOnlyAddressToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 创建智能合约SToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 多方签名MToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + toolStripSeparator12 + + + System.Windows.Forms.ToolStripSeparator, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 自定义CToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + toolStripSeparator6 + + + System.Windows.Forms.ToolStripSeparator, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 查看私钥VToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + viewContractToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + voteToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 复制到剪贴板CToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 删除DToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + toolStripStatusLabel1 + + + System.Windows.Forms.ToolStripStatusLabel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + lbl_height + + + System.Windows.Forms.ToolStripStatusLabel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + toolStripStatusLabel4 + + + System.Windows.Forms.ToolStripStatusLabel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + lbl_count_node + + + System.Windows.Forms.ToolStripStatusLabel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + toolStripProgressBar1 + + + System.Windows.Forms.ToolStripProgressBar, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + toolStripStatusLabel2 + + + System.Windows.Forms.ToolStripStatusLabel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + toolStripStatusLabel3 + + + System.Windows.Forms.ToolStripStatusLabel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + timer1 + + + System.Windows.Forms.Timer, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + columnHeader2 + + + System.Windows.Forms.ColumnHeader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + columnHeader6 + + + System.Windows.Forms.ColumnHeader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + columnHeader3 + + + System.Windows.Forms.ColumnHeader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + columnHeader5 + + + System.Windows.Forms.ColumnHeader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + columnHeader7 + + + System.Windows.Forms.ColumnHeader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + columnHeader8 + + + System.Windows.Forms.ColumnHeader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + columnHeader9 + + + System.Windows.Forms.ColumnHeader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + columnHeader10 + + + System.Windows.Forms.ColumnHeader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + toolStripMenuItem1 + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + MainForm + + + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + diff --git a/src/Neo.GUI/GUI/MainForm.zh-Hans.resx b/src/Neo.GUI/GUI/MainForm.zh-Hans.resx new file mode 100644 index 0000000000..086c4e916a --- /dev/null +++ b/src/Neo.GUI/GUI/MainForm.zh-Hans.resx @@ -0,0 +1,445 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + 187, 22 + + + 创建钱包数据库(&N)... + + + 187, 22 + + + 打开钱包数据库(&O)... + + + 184, 6 + + + 187, 22 + + + 修改密码(&C)... + + + 184, 6 + + + 187, 22 + + + 退出(&X) + + + 64, 21 + + + 钱包(&W) + + + 124, 22 + + + 转账(&T)... + + + 121, 6 + + + 124, 22 + + + 签名(&S)... + + + 59, 21 + + + 交易(&T) + + + 150, 22 + + + 部署合约(&D)... + + + 150, 22 + + + 调用合约(&V)... + + + 147, 6 + + + 150, 22 + + + 选举(&E)... + + + 150, 22 + + + 消息签名(&S)... + + + 147, 6 + + + 150, 22 + + + 选项(&O)... + + + 60, 21 + + + 高级(&A) + + + 191, 22 + + + 查看帮助(&H) + + + 191, 22 + + + 官网(&W) + + + 188, 6 + + + 191, 22 + + + 开发人员工具(&T) + + + 191, 22 + + + 控制台(&C) + + + 188, 6 + + + 191, 22 + + + 关于&NEO + + + 61, 21 + + + 帮助(&H) + + + 地址 + + + 164, 22 + + + 创建新地址(&N) + + + 173, 22 + + + 导入&WIF... + + + 170, 6 + + + 173, 22 + + + 导入监视地址(&A)... + + + 164, 22 + + + 导入(&I) + + + 153, 22 + + + 多方签名(&M)... + + + 150, 6 + + + 153, 22 + + + 自定义(&C)... + + + 164, 22 + + + 创建合约地址(&A) + + + 161, 6 + + + 164, 22 + + + 查看私钥(&P) + + + 164, 22 + + + 查看合约(&O) + + + 164, 22 + + + 投票(&V)... + + + 164, 22 + + + 复制到剪贴板(&C) + + + 164, 22 + + + 删除(&D)... + + + 165, 186 + + + + AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w + LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACJTeXN0 + ZW0uV2luZG93cy5Gb3Jtcy5MaXN0Vmlld0dyb3VwBAAAAAZIZWFkZXIPSGVhZGVyQWxpZ25tZW50A1Rh + ZwROYW1lAQQCAShTeXN0ZW0uV2luZG93cy5Gb3Jtcy5Ib3Jpem9udGFsQWxpZ25tZW50AgAAAAIAAAAG + AwAAAAzmoIflh4botKbmiLcF/P///yhTeXN0ZW0uV2luZG93cy5Gb3Jtcy5Ib3Jpem9udGFsQWxpZ25t + ZW50AQAAAAd2YWx1ZV9fAAgCAAAAAAAAAAoGBQAAABVzdGFuZGFyZENvbnRyYWN0R3JvdXAL + + + + + AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w + LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACJTeXN0 + ZW0uV2luZG93cy5Gb3Jtcy5MaXN0Vmlld0dyb3VwBAAAAAZIZWFkZXIPSGVhZGVyQWxpZ25tZW50A1Rh + ZwROYW1lAQQCAShTeXN0ZW0uV2luZG93cy5Gb3Jtcy5Ib3Jpem9udGFsQWxpZ25tZW50AgAAAAIAAAAG + AwAAAAzlkIjnuqblnLDlnYAF/P///yhTeXN0ZW0uV2luZG93cy5Gb3Jtcy5Ib3Jpem9udGFsQWxpZ25t + ZW50AQAAAAd2YWx1ZV9fAAgCAAAAAAAAAAoGBQAAABhub25zdGFuZGFyZENvbnRyYWN0R3JvdXAL + + + + + AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w + LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACJTeXN0 + ZW0uV2luZG93cy5Gb3Jtcy5MaXN0Vmlld0dyb3VwBAAAAAZIZWFkZXIPSGVhZGVyQWxpZ25tZW50A1Rh + ZwROYW1lAQQCAShTeXN0ZW0uV2luZG93cy5Gb3Jtcy5Ib3Jpem9udGFsQWxpZ25tZW50AgAAAAIAAAAG + AwAAAAznm5Hop4blnLDlnYAF/P///yhTeXN0ZW0uV2luZG93cy5Gb3Jtcy5Ib3Jpem9udGFsQWxpZ25t + ZW50AQAAAAd2YWx1ZV9fAAgCAAAAAAAAAAoGBQAAAA53YXRjaE9ubHlHcm91cAs= + + + + 35, 17 + + + 高度: + + + 47, 17 + + + 连接数: + + + 95, 17 + + + 等待下一个区块: + + + 68, 17 + + + 发现新版本 + + + 账户 + + + 资产 + + + 类型 + + + 余额 + + + 发行者 + + + 资产 + + + 时间 + + + 交易编号 + + + 确认 + + + 交易类型 + + + 137, 22 + + + 复制交易ID + + + 138, 26 + + + 交易记录 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/OpenWalletDialog.cs b/src/Neo.GUI/GUI/OpenWalletDialog.cs new file mode 100644 index 0000000000..a43352a9ed --- /dev/null +++ b/src/Neo.GUI/GUI/OpenWalletDialog.cs @@ -0,0 +1,66 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// OpenWalletDialog.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Windows.Forms; + +namespace Neo.GUI +{ + internal partial class OpenWalletDialog : Form + { + public OpenWalletDialog() + { + InitializeComponent(); + } + + public string Password + { + get + { + return textBox2.Text; + } + set + { + textBox2.Text = value; + } + } + + public string WalletPath + { + get + { + return textBox1.Text; + } + set + { + textBox1.Text = value; + } + } + + private void textBox_TextChanged(object sender, EventArgs e) + { + if (textBox1.TextLength == 0 || textBox2.TextLength == 0) + { + button2.Enabled = false; + return; + } + button2.Enabled = true; + } + + private void button1_Click(object sender, EventArgs e) + { + if (openFileDialog1.ShowDialog() == DialogResult.OK) + { + textBox1.Text = openFileDialog1.FileName; + } + } + } +} diff --git a/src/Neo.GUI/GUI/OpenWalletDialog.designer.cs b/src/Neo.GUI/GUI/OpenWalletDialog.designer.cs new file mode 100644 index 0000000000..a1cc1a1308 --- /dev/null +++ b/src/Neo.GUI/GUI/OpenWalletDialog.designer.cs @@ -0,0 +1,125 @@ +// Copyright (C) 2016-2024 The Neo Project. +// +// The neo-gui is free software distributed under the MIT software +// license, see the accompanying file LICENSE in the main directory of +// the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.GUI +{ + partial class OpenWalletDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(OpenWalletDialog)); + this.button2 = new System.Windows.Forms.Button(); + this.textBox2 = new System.Windows.Forms.TextBox(); + this.label2 = new System.Windows.Forms.Label(); + this.button1 = new System.Windows.Forms.Button(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.label1 = new System.Windows.Forms.Label(); + this.openFileDialog1 = new System.Windows.Forms.OpenFileDialog(); + this.SuspendLayout(); + // + // button2 + // + resources.ApplyResources(this.button2, "button2"); + this.button2.DialogResult = System.Windows.Forms.DialogResult.OK; + this.button2.Name = "button2"; + this.button2.UseVisualStyleBackColor = true; + // + // textBox2 + // + resources.ApplyResources(this.textBox2, "textBox2"); + this.textBox2.Name = "textBox2"; + this.textBox2.UseSystemPasswordChar = true; + this.textBox2.TextChanged += new System.EventHandler(this.textBox_TextChanged); + // + // label2 + // + resources.ApplyResources(this.label2, "label2"); + this.label2.Name = "label2"; + // + // button1 + // + resources.ApplyResources(this.button1, "button1"); + this.button1.Name = "button1"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // textBox1 + // + resources.ApplyResources(this.textBox1, "textBox1"); + this.textBox1.Name = "textBox1"; + this.textBox1.ReadOnly = true; + this.textBox1.TextChanged += new System.EventHandler(this.textBox_TextChanged); + // + // label1 + // + resources.ApplyResources(this.label1, "label1"); + this.label1.Name = "label1"; + // + // openFileDialog1 + // + this.openFileDialog1.DefaultExt = "json"; + resources.ApplyResources(this.openFileDialog1, "openFileDialog1"); + // + // OpenWalletDialog + // + this.AcceptButton = this.button2; + resources.ApplyResources(this, "$this"); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.button2); + this.Controls.Add(this.textBox2); + this.Controls.Add(this.label2); + this.Controls.Add(this.button1); + this.Controls.Add(this.textBox1); + this.Controls.Add(this.label1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "OpenWalletDialog"; + this.ShowInTaskbar = false; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Button button2; + private System.Windows.Forms.TextBox textBox2; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.OpenFileDialog openFileDialog1; + } +} diff --git a/src/Neo.GUI/GUI/OpenWalletDialog.es-ES.resx b/src/Neo.GUI/GUI/OpenWalletDialog.es-ES.resx new file mode 100644 index 0000000000..bf023cf9a0 --- /dev/null +++ b/src/Neo.GUI/GUI/OpenWalletDialog.es-ES.resx @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + 142, 41 + + + 65, 44 + + + 71, 16 + + + Contraseña: + + + Buscar... + + + 142, 12 + + + 204, 23 + + + 124, 16 + + + Fichero de nomedero: + + + Abrir monedero + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/OpenWalletDialog.resx b/src/Neo.GUI/GUI/OpenWalletDialog.resx new file mode 100644 index 0000000000..8b5a8e2e01 --- /dev/null +++ b/src/Neo.GUI/GUI/OpenWalletDialog.resx @@ -0,0 +1,315 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + 8 + + + 15 + + + Password: + + + 5 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + 7, 16 + + + + Top, Left, Right + + + True + + + OK + + + 16, 44 + + + 352, 68 + + + $this + + + Wallet File: + + + button2 + + + OpenWalletDialog + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Top, Right + + + $this + + + $this + + + 12, 15 + + + textBox1 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 263, 23 + + + 439, 103 + + + System.Windows.Forms.OpenFileDialog, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Bottom, Right + + + CenterScreen + + + 75, 23 + + + openFileDialog1 + + + textBox2 + + + label1 + + + label2 + + + 75, 23 + + + 9 + + + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Browse + + + False + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + NEP-6 Wallet|*.json|SQLite Wallet|*.db3 + + + 83, 12 + + + 61, 16 + + + 3 + + + 150, 23 + + + True + + + 65, 16 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 1 + + + 11 + + + 83, 41 + + + 4 + + + 10 + + + button1 + + + 0 + + + $this + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 352, 12 + + + 微软雅黑, 9pt + + + Open Wallet + + + 2 + + + 12 + + + $this + + + True + + + 17, 17 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/OpenWalletDialog.zh-Hans.resx b/src/Neo.GUI/GUI/OpenWalletDialog.zh-Hans.resx new file mode 100644 index 0000000000..5c4bf63eda --- /dev/null +++ b/src/Neo.GUI/GUI/OpenWalletDialog.zh-Hans.resx @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + 确定 + + + 101, 41 + + + 60, 44 + + + 35, 16 + + + 密码: + + + 浏览 + + + 101, 12 + + + 245, 23 + + + 83, 16 + + + 钱包文件位置: + + + NEP-6钱包文件|*.json|SQLite钱包文件|*.db3 + + + 打开钱包 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/ParametersEditor.Designer.cs b/src/Neo.GUI/GUI/ParametersEditor.Designer.cs new file mode 100644 index 0000000000..6f0b2432e4 --- /dev/null +++ b/src/Neo.GUI/GUI/ParametersEditor.Designer.cs @@ -0,0 +1,200 @@ +// Copyright (C) 2016-2024 The Neo Project. +// +// The neo-gui is free software distributed under the MIT software +// license, see the accompanying file LICENSE in the main directory of +// the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.GUI +{ + partial class ParametersEditor + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ParametersEditor)); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.listView1 = new System.Windows.Forms.ListView(); + this.columnHeader1 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnHeader2 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnHeader3 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.panel1 = new System.Windows.Forms.Panel(); + this.button4 = new System.Windows.Forms.Button(); + this.button3 = new System.Windows.Forms.Button(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.groupBox3 = new System.Windows.Forms.GroupBox(); + this.button2 = new System.Windows.Forms.Button(); + this.button1 = new System.Windows.Forms.Button(); + this.textBox2 = new System.Windows.Forms.TextBox(); + this.groupBox1.SuspendLayout(); + this.panel1.SuspendLayout(); + this.groupBox2.SuspendLayout(); + this.groupBox3.SuspendLayout(); + this.SuspendLayout(); + // + // groupBox1 + // + resources.ApplyResources(this.groupBox1, "groupBox1"); + this.groupBox1.Controls.Add(this.listView1); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.TabStop = false; + // + // listView1 + // + resources.ApplyResources(this.listView1, "listView1"); + this.listView1.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { + this.columnHeader1, + this.columnHeader2, + this.columnHeader3}); + this.listView1.FullRowSelect = true; + this.listView1.GridLines = true; + this.listView1.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable; + this.listView1.MultiSelect = false; + this.listView1.Name = "listView1"; + this.listView1.ShowGroups = false; + this.listView1.UseCompatibleStateImageBehavior = false; + this.listView1.View = System.Windows.Forms.View.Details; + this.listView1.SelectedIndexChanged += new System.EventHandler(this.listView1_SelectedIndexChanged); + // + // columnHeader1 + // + resources.ApplyResources(this.columnHeader1, "columnHeader1"); + // + // columnHeader2 + // + resources.ApplyResources(this.columnHeader2, "columnHeader2"); + // + // columnHeader3 + // + resources.ApplyResources(this.columnHeader3, "columnHeader3"); + // + // panel1 + // + resources.ApplyResources(this.panel1, "panel1"); + this.panel1.Controls.Add(this.button4); + this.panel1.Controls.Add(this.button3); + this.panel1.Name = "panel1"; + // + // button4 + // + resources.ApplyResources(this.button4, "button4"); + this.button4.Name = "button4"; + this.button4.UseVisualStyleBackColor = true; + this.button4.Click += new System.EventHandler(this.button4_Click); + // + // button3 + // + resources.ApplyResources(this.button3, "button3"); + this.button3.Name = "button3"; + this.button3.UseVisualStyleBackColor = true; + this.button3.Click += new System.EventHandler(this.button3_Click); + // + // groupBox2 + // + resources.ApplyResources(this.groupBox2, "groupBox2"); + this.groupBox2.Controls.Add(this.textBox1); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.TabStop = false; + // + // textBox1 + // + resources.ApplyResources(this.textBox1, "textBox1"); + this.textBox1.Name = "textBox1"; + this.textBox1.ReadOnly = true; + // + // groupBox3 + // + resources.ApplyResources(this.groupBox3, "groupBox3"); + this.groupBox3.Controls.Add(this.panel1); + this.groupBox3.Controls.Add(this.button2); + this.groupBox3.Controls.Add(this.button1); + this.groupBox3.Controls.Add(this.textBox2); + this.groupBox3.Name = "groupBox3"; + this.groupBox3.TabStop = false; + // + // button2 + // + resources.ApplyResources(this.button2, "button2"); + this.button2.Name = "button2"; + this.button2.UseVisualStyleBackColor = true; + this.button2.Click += new System.EventHandler(this.button2_Click); + // + // button1 + // + resources.ApplyResources(this.button1, "button1"); + this.button1.Name = "button1"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // textBox2 + // + resources.ApplyResources(this.textBox2, "textBox2"); + this.textBox2.Name = "textBox2"; + this.textBox2.TextChanged += new System.EventHandler(this.textBox2_TextChanged); + // + // ParametersEditor + // + resources.ApplyResources(this, "$this"); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.groupBox3); + this.Controls.Add(this.groupBox2); + this.Controls.Add(this.groupBox1); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "ParametersEditor"; + this.ShowInTaskbar = false; + this.groupBox1.ResumeLayout(false); + this.panel1.ResumeLayout(false); + this.groupBox2.ResumeLayout(false); + this.groupBox2.PerformLayout(); + this.groupBox3.ResumeLayout(false); + this.groupBox3.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.ListView listView1; + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.GroupBox groupBox3; + private System.Windows.Forms.TextBox textBox2; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.ColumnHeader columnHeader1; + private System.Windows.Forms.ColumnHeader columnHeader2; + private System.Windows.Forms.ColumnHeader columnHeader3; + private System.Windows.Forms.Button button2; + private System.Windows.Forms.Button button3; + private System.Windows.Forms.Button button4; + private System.Windows.Forms.Panel panel1; + } +} diff --git a/src/Neo.GUI/GUI/ParametersEditor.cs b/src/Neo.GUI/GUI/ParametersEditor.cs new file mode 100644 index 0000000000..19eac82c31 --- /dev/null +++ b/src/Neo.GUI/GUI/ParametersEditor.cs @@ -0,0 +1,198 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ParametersEditor.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Cryptography.ECC; +using Neo.SmartContract; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Numerics; +using System.Windows.Forms; + +namespace Neo.GUI +{ + internal partial class ParametersEditor : Form + { + private readonly IList parameters; + + public ParametersEditor(IList parameters) + { + InitializeComponent(); + this.parameters = parameters; + listView1.Items.AddRange(parameters.Select((p, i) => new ListViewItem(new[] + { + new ListViewItem.ListViewSubItem + { + Name = "index", + Text = $"[{i}]" + }, + new ListViewItem.ListViewSubItem + { + Name = "type", + Text = p.Type.ToString() + }, + new ListViewItem.ListViewSubItem + { + Name = "value", + Text = p.ToString() + } + }, -1) + { + Tag = p + }).ToArray()); + panel1.Enabled = !parameters.IsReadOnly; + } + + private void listView1_SelectedIndexChanged(object sender, EventArgs e) + { + if (listView1.SelectedIndices.Count > 0) + { + textBox1.Text = listView1.SelectedItems[0].SubItems["value"].Text; + textBox2.Enabled = ((ContractParameter)listView1.SelectedItems[0].Tag).Type != ContractParameterType.Array; + button2.Enabled = !textBox2.Enabled; + button4.Enabled = true; + } + else + { + textBox1.Clear(); + textBox2.Enabled = true; + button2.Enabled = false; + button4.Enabled = false; + } + textBox2.Clear(); + } + + private void textBox2_TextChanged(object sender, EventArgs e) + { + button1.Enabled = listView1.SelectedIndices.Count > 0 && textBox2.TextLength > 0; + button3.Enabled = textBox2.TextLength > 0; + } + + private void button1_Click(object sender, EventArgs e) + { + if (listView1.SelectedIndices.Count == 0) return; + ContractParameter parameter = (ContractParameter)listView1.SelectedItems[0].Tag; + try + { + parameter.SetValue(textBox2.Text); + listView1.SelectedItems[0].SubItems["value"].Text = parameter.ToString(); + textBox1.Text = listView1.SelectedItems[0].SubItems["value"].Text; + textBox2.Clear(); + } + catch (Exception err) + { + MessageBox.Show(err.Message); + } + } + + private void button2_Click(object sender, EventArgs e) + { + if (listView1.SelectedIndices.Count == 0) return; + ContractParameter parameter = (ContractParameter)listView1.SelectedItems[0].Tag; + using ParametersEditor dialog = new ParametersEditor((IList)parameter.Value); + dialog.ShowDialog(); + listView1.SelectedItems[0].SubItems["value"].Text = parameter.ToString(); + textBox1.Text = listView1.SelectedItems[0].SubItems["value"].Text; + } + + private void button3_Click(object sender, EventArgs e) + { + string s = textBox2.Text; + ContractParameter parameter = new ContractParameter(); + if (string.Equals(s, "true", StringComparison.OrdinalIgnoreCase)) + { + parameter.Type = ContractParameterType.Boolean; + parameter.Value = true; + } + else if (string.Equals(s, "false", StringComparison.OrdinalIgnoreCase)) + { + parameter.Type = ContractParameterType.Boolean; + parameter.Value = false; + } + else if (long.TryParse(s, out long num)) + { + parameter.Type = ContractParameterType.Integer; + parameter.Value = num; + } + else if (s.StartsWith("0x")) + { + if (UInt160.TryParse(s, out UInt160 i160)) + { + parameter.Type = ContractParameterType.Hash160; + parameter.Value = i160; + } + else if (UInt256.TryParse(s, out UInt256 i256)) + { + parameter.Type = ContractParameterType.Hash256; + parameter.Value = i256; + } + else if (BigInteger.TryParse(s.Substring(2), NumberStyles.AllowHexSpecifier, null, out BigInteger bi)) + { + parameter.Type = ContractParameterType.Integer; + parameter.Value = bi; + } + else + { + parameter.Type = ContractParameterType.String; + parameter.Value = s; + } + } + else if (ECPoint.TryParse(s, ECCurve.Secp256r1, out ECPoint point)) + { + parameter.Type = ContractParameterType.PublicKey; + parameter.Value = point; + } + else + { + try + { + parameter.Value = s.HexToBytes(); + parameter.Type = ContractParameterType.ByteArray; + } + catch (FormatException) + { + parameter.Type = ContractParameterType.String; + parameter.Value = s; + } + } + parameters.Add(parameter); + listView1.Items.Add(new ListViewItem(new[] + { + new ListViewItem.ListViewSubItem + { + Name = "index", + Text = $"[{listView1.Items.Count}]" + }, + new ListViewItem.ListViewSubItem + { + Name = "type", + Text = parameter.Type.ToString() + }, + new ListViewItem.ListViewSubItem + { + Name = "value", + Text = parameter.ToString() + } + }, -1) + { + Tag = parameter + }); + } + + private void button4_Click(object sender, EventArgs e) + { + int index = listView1.SelectedIndices[0]; + parameters.RemoveAt(index); + listView1.Items.RemoveAt(index); + } + } +} diff --git a/src/Neo.GUI/GUI/ParametersEditor.es-ES.resx b/src/Neo.GUI/GUI/ParametersEditor.es-ES.resx new file mode 100644 index 0000000000..21e7fad664 --- /dev/null +++ b/src/Neo.GUI/GUI/ParametersEditor.es-ES.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Tipo + + + Valor + + + Lista de parámetros + + + Valor anterior + + + Actualizar + + + Nuevo valor + + + Definir parámetros + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/ParametersEditor.resx b/src/Neo.GUI/GUI/ParametersEditor.resx new file mode 100644 index 0000000000..a2f2d31c9e --- /dev/null +++ b/src/Neo.GUI/GUI/ParametersEditor.resx @@ -0,0 +1,489 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + False + + + + + + + 661, 485 + + + ParametersEditor + + + False + + + False + + + + + + + System.Windows.Forms.ColumnHeader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox3 + + + panel1 + + + Value + + + 2 + + + 0 + + + Update + + + 0 + + + + Top, Bottom, Left, Right + + + 1 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 233, 126 + + + 3 + + + groupBox3 + + + True + + + 3, 22 + + + 23, 23 + + + 7, 17 + + + 1 + + + 74, 23 + + + 3, 4, 3, 4 + + + panel1 + + + Bottom, Right + + + 0 + + + Type + + + CenterScreen + + + Top, Bottom, Left, Right + + + False + + + textBox1 + + + Parameter List + + + 75, 23 + + + groupBox3 + + + 75, 23 + + + 380, 436 + + + 233, 250 + + + 0 + + + 3, 19 + + + 410, 290 + + + 0 + + + System.Windows.Forms.ListView, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + panel1 + + + 29, 0 + + + 1 + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 80, 154 + + + 161, 154 + + + 3 + + + Edit Array + + + Bottom, Left, Right + + + Old Value + + + 410, 12 + + + groupBox2 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 微软雅黑, 9pt + + + groupBox3 + + + 2 + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + button1 + + + listView1 + + + columnHeader3 + + + - + + + 12, 12 + + + columnHeader1 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 392, 461 + + + 0 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Fill + + + 0 + + + 3, 154 + + + True + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Top, Bottom, Left, Right + + + System.Windows.Forms.Panel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Bottom, Left, Right + + + $this + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + NoControl + + + System.Windows.Forms.ColumnHeader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 0 + + + 0, 0 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 239, 272 + + + 200 + + + columnHeader2 + + + 50 + + + groupBox2 + + + button2 + + + groupBox3 + + + 2 + + + button4 + + + 1 + + + 23, 23 + + + 1 + + + 2 + + + Bottom, Right + + + Set Parameters + + + groupBox1 + + + groupBox1 + + + 0, 0, 0, 0 + + + New Value + + + 0 + + + 100 + + + $this + + + textBox2 + + + 1 + + + $this + + + 2 + + + Top, Bottom, Left + + + button3 + + + 6, 19 + + + 239, 183 + + + System.Windows.Forms.ColumnHeader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + True + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/ParametersEditor.zh-Hans.resx b/src/Neo.GUI/GUI/ParametersEditor.zh-Hans.resx new file mode 100644 index 0000000000..8db893dbac --- /dev/null +++ b/src/Neo.GUI/GUI/ParametersEditor.zh-Hans.resx @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 参数列表 + + + 类型 + + + + + + 当前值 + + + 新值 + + + 编辑数组 + + + 更新 + + + 设置参数 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/PayToDialog.Designer.cs b/src/Neo.GUI/GUI/PayToDialog.Designer.cs new file mode 100644 index 0000000000..2bf4e075c0 --- /dev/null +++ b/src/Neo.GUI/GUI/PayToDialog.Designer.cs @@ -0,0 +1,142 @@ +// Copyright (C) 2016-2024 The Neo Project. +// +// The neo-gui is free software distributed under the MIT software +// license, see the accompanying file LICENSE in the main directory of +// the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.GUI +{ + partial class PayToDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(PayToDialog)); + this.label1 = new System.Windows.Forms.Label(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.label2 = new System.Windows.Forms.Label(); + this.textBox2 = new System.Windows.Forms.TextBox(); + this.button1 = new System.Windows.Forms.Button(); + this.label3 = new System.Windows.Forms.Label(); + this.comboBox1 = new System.Windows.Forms.ComboBox(); + this.label4 = new System.Windows.Forms.Label(); + this.textBox3 = new System.Windows.Forms.TextBox(); + this.SuspendLayout(); + // + // label1 + // + resources.ApplyResources(this.label1, "label1"); + this.label1.Name = "label1"; + // + // textBox1 + // + resources.ApplyResources(this.textBox1, "textBox1"); + this.textBox1.Name = "textBox1"; + this.textBox1.TextChanged += new System.EventHandler(this.textBox_TextChanged); + // + // label2 + // + resources.ApplyResources(this.label2, "label2"); + this.label2.Name = "label2"; + // + // textBox2 + // + resources.ApplyResources(this.textBox2, "textBox2"); + this.textBox2.Name = "textBox2"; + this.textBox2.TextChanged += new System.EventHandler(this.textBox_TextChanged); + // + // button1 + // + resources.ApplyResources(this.button1, "button1"); + this.button1.DialogResult = System.Windows.Forms.DialogResult.OK; + this.button1.Name = "button1"; + this.button1.UseVisualStyleBackColor = true; + // + // label3 + // + resources.ApplyResources(this.label3, "label3"); + this.label3.Name = "label3"; + // + // comboBox1 + // + resources.ApplyResources(this.comboBox1, "comboBox1"); + this.comboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboBox1.FormattingEnabled = true; + this.comboBox1.Name = "comboBox1"; + this.comboBox1.SelectedIndexChanged += new System.EventHandler(this.comboBox1_SelectedIndexChanged); + // + // label4 + // + resources.ApplyResources(this.label4, "label4"); + this.label4.Name = "label4"; + // + // textBox3 + // + resources.ApplyResources(this.textBox3, "textBox3"); + this.textBox3.Name = "textBox3"; + this.textBox3.ReadOnly = true; + // + // PayToDialog + // + this.AcceptButton = this.button1; + resources.ApplyResources(this, "$this"); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.textBox3); + this.Controls.Add(this.label4); + this.Controls.Add(this.comboBox1); + this.Controls.Add(this.label3); + this.Controls.Add(this.button1); + this.Controls.Add(this.textBox2); + this.Controls.Add(this.label2); + this.Controls.Add(this.textBox1); + this.Controls.Add(this.label1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "PayToDialog"; + this.ShowInTaskbar = false; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.TextBox textBox2; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.ComboBox comboBox1; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.TextBox textBox3; + } +} diff --git a/src/Neo.GUI/GUI/PayToDialog.cs b/src/Neo.GUI/GUI/PayToDialog.cs new file mode 100644 index 0000000000..ad024b7ca5 --- /dev/null +++ b/src/Neo.GUI/GUI/PayToDialog.cs @@ -0,0 +1,106 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// PayToDialog.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Wallets; +using System; +using System.Windows.Forms; +using static Neo.Program; + +namespace Neo.GUI +{ + internal partial class PayToDialog : Form + { + public PayToDialog(AssetDescriptor asset = null, UInt160 scriptHash = null) + { + InitializeComponent(); + if (asset is null) + { + foreach (UInt160 assetId in NEP5Watched) + { + try + { + comboBox1.Items.Add(new AssetDescriptor(Service.NeoSystem.StoreView, Service.NeoSystem.Settings, assetId)); + } + catch (ArgumentException) + { + continue; + } + } + } + else + { + comboBox1.Items.Add(asset); + comboBox1.SelectedIndex = 0; + comboBox1.Enabled = false; + } + if (scriptHash != null) + { + textBox1.Text = scriptHash.ToAddress(Service.NeoSystem.Settings.AddressVersion); + textBox1.ReadOnly = true; + } + } + + public TxOutListBoxItem GetOutput() + { + AssetDescriptor asset = (AssetDescriptor)comboBox1.SelectedItem; + return new TxOutListBoxItem + { + AssetName = asset.AssetName, + AssetId = asset.AssetId, + Value = BigDecimal.Parse(textBox2.Text, asset.Decimals), + ScriptHash = textBox1.Text.ToScriptHash(Service.NeoSystem.Settings.AddressVersion) + }; + } + + private void comboBox1_SelectedIndexChanged(object sender, EventArgs e) + { + if (comboBox1.SelectedItem is AssetDescriptor asset) + { + textBox3.Text = Service.CurrentWallet.GetAvailable(Service.NeoSystem.StoreView, asset.AssetId).ToString(); + } + else + { + textBox3.Text = ""; + } + textBox_TextChanged(this, EventArgs.Empty); + } + + private void textBox_TextChanged(object sender, EventArgs e) + { + if (comboBox1.SelectedIndex < 0 || textBox1.TextLength == 0 || textBox2.TextLength == 0) + { + button1.Enabled = false; + return; + } + try + { + textBox1.Text.ToScriptHash(Service.NeoSystem.Settings.AddressVersion); + } + catch (FormatException) + { + button1.Enabled = false; + return; + } + AssetDescriptor asset = (AssetDescriptor)comboBox1.SelectedItem; + if (!BigDecimal.TryParse(textBox2.Text, asset.Decimals, out BigDecimal amount)) + { + button1.Enabled = false; + return; + } + if (amount.Sign <= 0) + { + button1.Enabled = false; + return; + } + button1.Enabled = true; + } + } +} diff --git a/src/Neo.GUI/GUI/PayToDialog.es-ES.resx b/src/Neo.GUI/GUI/PayToDialog.es-ES.resx new file mode 100644 index 0000000000..525ae78e9a --- /dev/null +++ b/src/Neo.GUI/GUI/PayToDialog.es-ES.resx @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + 12, 88 + + + 56, 17 + + + Pagar a: + + + 28, 122 + + + 40, 17 + + + Total: + + + Aceptar + + + 22, 17 + + + 46, 17 + + + Activo: + + + 24, 54 + + + 44, 17 + + + Saldo: + + + Pago + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/PayToDialog.resx b/src/Neo.GUI/GUI/PayToDialog.resx new file mode 100644 index 0000000000..2c575df5ab --- /dev/null +++ b/src/Neo.GUI/GUI/PayToDialog.resx @@ -0,0 +1,384 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + True + + + + 21, 88 + + + 47, 17 + + + 4 + + + Pay to: + + + label1 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 8 + + + + Top, Left, Right + + + 74, 85 + + + 468, 23 + + + 5 + + + textBox1 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 7 + + + True + + + 12, 122 + + + 56, 17 + + + 6 + + + Amount: + + + label2 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 6 + + + Top, Left, Right + + + 74, 119 + + + 468, 23 + + + 7 + + + textBox2 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 5 + + + Top, Right + + + False + + + 467, 157 + + + 75, 23 + + + 8 + + + OK + + + button1 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 4 + + + True + + + 26, 17 + + + 42, 17 + + + 0 + + + Asset: + + + label3 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 3 + + + Top, Left, Right + + + 74, 14 + + + 468, 25 + + + 1 + + + comboBox1 + + + System.Windows.Forms.ComboBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 2 + + + True + + + 12, 54 + + + 56, 17 + + + 2 + + + Balance: + + + label4 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 1 + + + Top, Left, Right + + + 74, 51 + + + 468, 23 + + + 3 + + + textBox3 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 0 + + + True + + + 7, 17 + + + 554, 192 + + + 微软雅黑, 9pt + + + 3, 4, 3, 4 + + + CenterScreen + + + Payment + + + PayToDialog + + + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/PayToDialog.zh-Hans.resx b/src/Neo.GUI/GUI/PayToDialog.zh-Hans.resx new file mode 100644 index 0000000000..9f55c74270 --- /dev/null +++ b/src/Neo.GUI/GUI/PayToDialog.zh-Hans.resx @@ -0,0 +1,193 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + 12, 75 + + + 59, 17 + + + 对方账户: + + + 77, 72 + + + 308, 23 + + + 36, 104 + + + 35, 17 + + + 数额: + + + 77, 101 + + + 227, 23 + + + 310, 101 + + + 确定 + + + 36, 15 + + + 35, 17 + + + 资产: + + + 77, 12 + + + 308, 25 + + + 36, 46 + + + 35, 17 + + + 余额: + + + 77, 43 + + + 308, 23 + + + 397, 134 + + + 支付 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/QueueReader.cs b/src/Neo.GUI/GUI/QueueReader.cs new file mode 100644 index 0000000000..eac36f35f4 --- /dev/null +++ b/src/Neo.GUI/GUI/QueueReader.cs @@ -0,0 +1,49 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// QueueReader.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Collections.Generic; +using System.IO; +using System.Threading; + +namespace Neo.GUI +{ + internal class QueueReader : TextReader + { + private readonly Queue queue = new Queue(); + private string current; + private int index; + + public void Enqueue(string str) + { + queue.Enqueue(str); + } + + public override int Peek() + { + while (string.IsNullOrEmpty(current)) + { + while (!queue.TryDequeue(out current)) + Thread.Sleep(100); + index = 0; + } + return current[index]; + } + + public override int Read() + { + int c = Peek(); + if (c != -1) + if (++index >= current.Length) + current = null; + return c; + } + } +} diff --git a/src/Neo.GUI/GUI/SigningDialog.Designer.cs b/src/Neo.GUI/GUI/SigningDialog.Designer.cs new file mode 100644 index 0000000000..dabb436d23 --- /dev/null +++ b/src/Neo.GUI/GUI/SigningDialog.Designer.cs @@ -0,0 +1,172 @@ +// Copyright (C) 2016-2024 The Neo Project. +// +// The neo-gui is free software distributed under the MIT software +// license, see the accompanying file LICENSE in the main directory of +// the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.GUI +{ + partial class SigningDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(SigningDialog)); + this.button1 = new System.Windows.Forms.Button(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.textBox2 = new System.Windows.Forms.TextBox(); + this.button2 = new System.Windows.Forms.Button(); + this.button3 = new System.Windows.Forms.Button(); + this.cmbFormat = new System.Windows.Forms.ComboBox(); + this.cmbAddress = new System.Windows.Forms.ComboBox(); + this.label1 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.groupBox1.SuspendLayout(); + this.groupBox2.SuspendLayout(); + this.SuspendLayout(); + // + // button1 + // + resources.ApplyResources(this.button1, "button1"); + this.button1.Name = "button1"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // groupBox1 + // + resources.ApplyResources(this.groupBox1, "groupBox1"); + this.groupBox1.Controls.Add(this.textBox1); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.TabStop = false; + // + // textBox1 + // + resources.ApplyResources(this.textBox1, "textBox1"); + this.textBox1.Name = "textBox1"; + // + // groupBox2 + // + resources.ApplyResources(this.groupBox2, "groupBox2"); + this.groupBox2.Controls.Add(this.textBox2); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.TabStop = false; + // + // textBox2 + // + resources.ApplyResources(this.textBox2, "textBox2"); + this.textBox2.Name = "textBox2"; + this.textBox2.ReadOnly = true; + // + // button2 + // + resources.ApplyResources(this.button2, "button2"); + this.button2.Name = "button2"; + this.button2.UseVisualStyleBackColor = true; + this.button2.Click += new System.EventHandler(this.button2_Click); + // + // button3 + // + resources.ApplyResources(this.button3, "button3"); + this.button3.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.button3.Name = "button3"; + this.button3.UseVisualStyleBackColor = true; + // + // cmbFormat + // + resources.ApplyResources(this.cmbFormat, "cmbFormat"); + this.cmbFormat.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cmbFormat.FormattingEnabled = true; + this.cmbFormat.Items.AddRange(new object[] { + resources.GetString("cmbFormat.Items"), + resources.GetString("cmbFormat.Items1")}); + this.cmbFormat.Name = "cmbFormat"; + // + // cmbAddress + // + resources.ApplyResources(this.cmbAddress, "cmbAddress"); + this.cmbAddress.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cmbAddress.FormattingEnabled = true; + this.cmbAddress.Name = "cmbAddress"; + // + // label1 + // + resources.ApplyResources(this.label1, "label1"); + this.label1.Name = "label1"; + // + // label2 + // + resources.ApplyResources(this.label2, "label2"); + this.label2.Name = "label2"; + // + // SigningDialog + // + resources.ApplyResources(this, "$this"); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.button3; + this.Controls.Add(this.label2); + this.Controls.Add(this.label1); + this.Controls.Add(this.cmbAddress); + this.Controls.Add(this.cmbFormat); + this.Controls.Add(this.button3); + this.Controls.Add(this.button2); + this.Controls.Add(this.groupBox2); + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.button1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "SigningDialog"; + this.ShowInTaskbar = false; + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.groupBox2.ResumeLayout(false); + this.groupBox2.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Button button1; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.TextBox textBox2; + private System.Windows.Forms.Button button2; + private System.Windows.Forms.Button button3; + private System.Windows.Forms.ComboBox cmbFormat; + private System.Windows.Forms.ComboBox cmbAddress; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label label2; + } +} diff --git a/src/Neo.GUI/GUI/SigningDialog.cs b/src/Neo.GUI/GUI/SigningDialog.cs new file mode 100644 index 0000000000..5b515a4365 --- /dev/null +++ b/src/Neo.GUI/GUI/SigningDialog.cs @@ -0,0 +1,107 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// SigningDialog.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Cryptography; +using Neo.Properties; +using Neo.Wallets; +using System; +using System.Linq; +using System.Text; +using System.Windows.Forms; +using static Neo.Program; + +namespace Neo.GUI +{ + internal partial class SigningDialog : Form + { + private class WalletEntry + { + public WalletAccount Account; + + public override string ToString() + { + if (!string.IsNullOrEmpty(Account.Label)) + { + return $"[{Account.Label}] " + Account.Address; + } + return Account.Address; + } + } + + + public SigningDialog() + { + InitializeComponent(); + + cmbFormat.SelectedIndex = 0; + cmbAddress.Items.AddRange(Service.CurrentWallet.GetAccounts() + .Where(u => u.HasKey) + .Select(u => new WalletEntry() { Account = u }) + .ToArray()); + + if (cmbAddress.Items.Count > 0) + { + cmbAddress.SelectedIndex = 0; + } + else + { + textBox2.Enabled = false; + button1.Enabled = false; + } + } + + private void button1_Click(object sender, EventArgs e) + { + if (textBox1.Text == "") + { + MessageBox.Show(Strings.SigningFailedNoDataMessage); + return; + } + + byte[] raw, signedData; + try + { + switch (cmbFormat.SelectedIndex) + { + case 0: raw = Encoding.UTF8.GetBytes(textBox1.Text); break; + case 1: raw = textBox1.Text.HexToBytes(); break; + default: return; + } + } + catch (Exception err) + { + MessageBox.Show(err.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + var account = (WalletEntry)cmbAddress.SelectedItem; + var keys = account.Account.GetKey(); + + try + { + signedData = Crypto.Sign(raw, keys.PrivateKey); + } + catch (Exception err) + { + MessageBox.Show(err.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + textBox2.Text = signedData?.ToHexString(); + } + + private void button2_Click(object sender, EventArgs e) + { + textBox2.SelectAll(); + textBox2.Copy(); + } + } +} diff --git a/src/Neo.GUI/GUI/SigningDialog.es-ES.resx b/src/Neo.GUI/GUI/SigningDialog.es-ES.resx new file mode 100644 index 0000000000..c440dbe4b2 --- /dev/null +++ b/src/Neo.GUI/GUI/SigningDialog.es-ES.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Firma + + + Entrada + + + Salida + + + Copiar + + + Cancelar + + + Emitir + + + Firma + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/SigningDialog.resx b/src/Neo.GUI/GUI/SigningDialog.resx new file mode 100644 index 0000000000..d462a50fac --- /dev/null +++ b/src/Neo.GUI/GUI/SigningDialog.resx @@ -0,0 +1,462 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Top + + + + 189, 269 + + + 75, 23 + + + + 2 + + + Sign + + + button1 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 8 + + + Top, Left, Right + + + Fill + + + 3, 19 + + + True + + + Vertical + + + 424, 139 + + + 1 + + + textBox1 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox1 + + + 0 + + + 12, 97 + + + 430, 161 + + + 3 + + + Input + + + groupBox1 + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 7 + + + Top, Bottom, Left, Right + + + Fill + + + 3, 19 + + + True + + + Vertical + + + 424, 117 + + + 1 + + + textBox2 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox2 + + + 0 + + + 12, 298 + + + 430, 139 + + + 4 + + + Output + + + groupBox2 + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 6 + + + Bottom, Right + + + 286, 453 + + + 75, 23 + + + 5 + + + Copy + + + button2 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 5 + + + Bottom, Right + + + 367, 453 + + + 75, 23 + + + 6 + + + Close + + + button3 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 4 + + + Text + + + Hex + + + 367, 48 + + + 2, 3, 2, 3 + + + 72, 25 + + + 7 + + + cmbFormat + + + System.Windows.Forms.ComboBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 3 + + + 15, 48 + + + 2, 3, 2, 3 + + + 349, 25 + + + 8 + + + cmbAddress + + + System.Windows.Forms.ComboBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 2 + + + True + + + 12, 21 + + + 2, 0, 2, 0 + + + 56, 17 + + + 9 + + + Address + + + label1 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 1 + + + True + + + NoControl + + + 364, 21 + + + 2, 0, 2, 0 + + + 49, 17 + + + 9 + + + Format + + + label2 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 0 + + + True + + + 7, 17 + + + 454, 488 + + + Microsoft YaHei UI, 9pt + + + 3, 4, 3, 4 + + + CenterScreen + + + Signature + + + SigningDialog + + + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/SigningDialog.zh-Hans.resx b/src/Neo.GUI/GUI/SigningDialog.zh-Hans.resx new file mode 100644 index 0000000000..282612d0f8 --- /dev/null +++ b/src/Neo.GUI/GUI/SigningDialog.zh-Hans.resx @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 签名 + + + 输入 + + + 输出 + + + 复制 + + + 关闭 + + + + 32, 17 + + + 地址 + + + 32, 17 + + + 格式 + + + 签名 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/SigningTxDialog.Designer.cs b/src/Neo.GUI/GUI/SigningTxDialog.Designer.cs new file mode 100644 index 0000000000..a8bb11f2c2 --- /dev/null +++ b/src/Neo.GUI/GUI/SigningTxDialog.Designer.cs @@ -0,0 +1,142 @@ +// Copyright (C) 2016-2024 The Neo Project. +// +// The neo-gui is free software distributed under the MIT software +// license, see the accompanying file LICENSE in the main directory of +// the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.GUI +{ + partial class SigningTxDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(SigningTxDialog)); + this.button1 = new System.Windows.Forms.Button(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.textBox2 = new System.Windows.Forms.TextBox(); + this.button2 = new System.Windows.Forms.Button(); + this.button3 = new System.Windows.Forms.Button(); + this.button4 = new System.Windows.Forms.Button(); + this.groupBox1.SuspendLayout(); + this.groupBox2.SuspendLayout(); + this.SuspendLayout(); + // + // button1 + // + resources.ApplyResources(this.button1, "button1"); + this.button1.Name = "button1"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // groupBox1 + // + resources.ApplyResources(this.groupBox1, "groupBox1"); + this.groupBox1.Controls.Add(this.textBox1); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.TabStop = false; + // + // textBox1 + // + resources.ApplyResources(this.textBox1, "textBox1"); + this.textBox1.Name = "textBox1"; + // + // groupBox2 + // + resources.ApplyResources(this.groupBox2, "groupBox2"); + this.groupBox2.Controls.Add(this.textBox2); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.TabStop = false; + // + // textBox2 + // + resources.ApplyResources(this.textBox2, "textBox2"); + this.textBox2.Name = "textBox2"; + this.textBox2.ReadOnly = true; + // + // button2 + // + resources.ApplyResources(this.button2, "button2"); + this.button2.Name = "button2"; + this.button2.UseVisualStyleBackColor = true; + this.button2.Click += new System.EventHandler(this.button2_Click); + // + // button3 + // + resources.ApplyResources(this.button3, "button3"); + this.button3.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.button3.Name = "button3"; + this.button3.UseVisualStyleBackColor = true; + // + // button4 + // + resources.ApplyResources(this.button4, "button4"); + this.button4.Name = "button4"; + this.button4.UseVisualStyleBackColor = true; + this.button4.Click += new System.EventHandler(this.button4_Click); + // + // SigningTxDialog + // + resources.ApplyResources(this, "$this"); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.button3; + this.Controls.Add(this.button4); + this.Controls.Add(this.button3); + this.Controls.Add(this.button2); + this.Controls.Add(this.groupBox2); + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.button1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "SigningTxDialog"; + this.ShowInTaskbar = false; + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.groupBox2.ResumeLayout(false); + this.groupBox2.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Button button1; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.TextBox textBox2; + private System.Windows.Forms.Button button2; + private System.Windows.Forms.Button button3; + private System.Windows.Forms.Button button4; + } +} diff --git a/src/Neo.GUI/GUI/SigningTxDialog.cs b/src/Neo.GUI/GUI/SigningTxDialog.cs new file mode 100644 index 0000000000..16e79efe58 --- /dev/null +++ b/src/Neo.GUI/GUI/SigningTxDialog.cs @@ -0,0 +1,66 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// SigningTxDialog.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Akka.Actor; +using Neo.Network.P2P.Payloads; +using Neo.Properties; +using Neo.SmartContract; +using System; +using System.Windows.Forms; +using static Neo.Program; + +namespace Neo.GUI +{ + internal partial class SigningTxDialog : Form + { + public SigningTxDialog() + { + InitializeComponent(); + } + + private void button1_Click(object sender, EventArgs e) + { + if (textBox1.Text == "") + { + MessageBox.Show(Strings.SigningFailedNoDataMessage); + return; + } + ContractParametersContext context = ContractParametersContext.Parse(textBox1.Text, Service.NeoSystem.StoreView); + if (!Service.CurrentWallet.Sign(context)) + { + MessageBox.Show(Strings.SigningFailedKeyNotFoundMessage); + return; + } + textBox2.Text = context.ToString(); + if (context.Completed) button4.Visible = true; + } + + private void button2_Click(object sender, EventArgs e) + { + textBox2.SelectAll(); + textBox2.Copy(); + } + + private void button4_Click(object sender, EventArgs e) + { + ContractParametersContext context = ContractParametersContext.Parse(textBox2.Text, Service.NeoSystem.StoreView); + if (!(context.Verifiable is Transaction tx)) + { + MessageBox.Show("Only support to broadcast transaction."); + return; + } + tx.Witnesses = context.GetWitnesses(); + Service.NeoSystem.Blockchain.Tell(tx); + InformationBox.Show(tx.Hash.ToString(), Strings.RelaySuccessText, Strings.RelaySuccessTitle); + button4.Visible = false; + } + } +} diff --git a/src/Neo.GUI/GUI/SigningTxDialog.es-ES.resx b/src/Neo.GUI/GUI/SigningTxDialog.es-ES.resx new file mode 100644 index 0000000000..c440dbe4b2 --- /dev/null +++ b/src/Neo.GUI/GUI/SigningTxDialog.es-ES.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Firma + + + Entrada + + + Salida + + + Copiar + + + Cancelar + + + Emitir + + + Firma + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/SigningTxDialog.resx b/src/Neo.GUI/GUI/SigningTxDialog.resx new file mode 100644 index 0000000000..1f1af30e86 --- /dev/null +++ b/src/Neo.GUI/GUI/SigningTxDialog.resx @@ -0,0 +1,372 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Top + + + + 190, 191 + + + 75, 23 + + + + 2 + + + Sign + + + button1 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 5 + + + Top, Left, Right + + + 12, 12 + + + 430, 173 + + + 3 + + + Input + + + groupBox1 + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 4 + + + Fill + + + 3, 19 + + + True + + + Vertical + + + 424, 151 + + + 1 + + + textBox1 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox1 + + + 0 + + + Top, Bottom, Left, Right + + + 12, 220 + + + 430, 227 + + + 4 + + + Output + + + groupBox2 + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 3 + + + Fill + + + 3, 19 + + + True + + + Vertical + + + 424, 205 + + + 1 + + + textBox2 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox2 + + + 0 + + + Bottom, Right + + + 286, 453 + + + 75, 23 + + + 5 + + + Copy + + + button2 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 2 + + + Bottom, Right + + + 367, 453 + + + 75, 23 + + + 6 + + + Close + + + button3 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 1 + + + 12, 453 + + + 75, 23 + + + 7 + + + Broadcast + + + False + + + button4 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 0 + + + True + + + 7, 17 + + + 454, 488 + + + 微软雅黑, 9pt + + + 3, 4, 3, 4 + + + CenterScreen + + + Signature + + + SigningTxDialog + + + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + diff --git a/src/Neo.GUI/GUI/SigningTxDialog.zh-Hans.resx b/src/Neo.GUI/GUI/SigningTxDialog.zh-Hans.resx new file mode 100644 index 0000000000..218f36f8e5 --- /dev/null +++ b/src/Neo.GUI/GUI/SigningTxDialog.zh-Hans.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 签名 + + + 输入 + + + 输出 + + + 复制 + + + 关闭 + + + 广播 + + + 签名 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/TextBoxWriter.cs b/src/Neo.GUI/GUI/TextBoxWriter.cs new file mode 100644 index 0000000000..bf1bfc9c40 --- /dev/null +++ b/src/Neo.GUI/GUI/TextBoxWriter.cs @@ -0,0 +1,40 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TextBoxWriter.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.IO; +using System.Text; +using System.Windows.Forms; + +namespace Neo.GUI +{ + internal class TextBoxWriter : TextWriter + { + private readonly TextBoxBase textBox; + + public override Encoding Encoding => Encoding.UTF8; + + public TextBoxWriter(TextBoxBase textBox) + { + this.textBox = textBox; + } + + public override void Write(char value) + { + textBox.Invoke(new Action(() => { textBox.Text += value; })); + } + + public override void Write(string value) + { + textBox.Invoke(new Action(textBox.AppendText), value); + } + } +} diff --git a/src/Neo.GUI/GUI/TransferDialog.Designer.cs b/src/Neo.GUI/GUI/TransferDialog.Designer.cs new file mode 100644 index 0000000000..8d18bb5316 --- /dev/null +++ b/src/Neo.GUI/GUI/TransferDialog.Designer.cs @@ -0,0 +1,131 @@ +// Copyright (C) 2016-2024 The Neo Project. +// +// The neo-gui is free software distributed under the MIT software +// license, see the accompanying file LICENSE in the main directory of +// the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.GUI +{ + partial class TransferDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TransferDialog)); + this.groupBox3 = new System.Windows.Forms.GroupBox(); + this.txOutListBox1 = new Neo.GUI.TxOutListBox(); + this.button4 = new System.Windows.Forms.Button(); + this.button3 = new System.Windows.Forms.Button(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.comboBoxFrom = new System.Windows.Forms.ComboBox(); + this.labelFrom = new System.Windows.Forms.Label(); + this.groupBox3.SuspendLayout(); + this.groupBox1.SuspendLayout(); + this.SuspendLayout(); + // + // groupBox3 + // + resources.ApplyResources(this.groupBox3, "groupBox3"); + this.groupBox3.Controls.Add(this.txOutListBox1); + this.groupBox3.Name = "groupBox3"; + this.groupBox3.TabStop = false; + // + // txOutListBox1 + // + resources.ApplyResources(this.txOutListBox1, "txOutListBox1"); + this.txOutListBox1.Asset = null; + this.txOutListBox1.Name = "txOutListBox1"; + this.txOutListBox1.ReadOnly = false; + this.txOutListBox1.ScriptHash = null; + this.txOutListBox1.ItemsChanged += new System.EventHandler(this.txOutListBox1_ItemsChanged); + // + // button4 + // + resources.ApplyResources(this.button4, "button4"); + this.button4.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.button4.Name = "button4"; + this.button4.UseVisualStyleBackColor = true; + // + // button3 + // + resources.ApplyResources(this.button3, "button3"); + this.button3.DialogResult = System.Windows.Forms.DialogResult.OK; + this.button3.Name = "button3"; + this.button3.UseVisualStyleBackColor = true; + // + // groupBox1 + // + resources.ApplyResources(this.groupBox1, "groupBox1"); + this.groupBox1.Controls.Add(this.comboBoxFrom); + this.groupBox1.Controls.Add(this.labelFrom); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.TabStop = false; + // + // comboBoxFrom + // + resources.ApplyResources(this.comboBoxFrom, "comboBoxFrom"); + this.comboBoxFrom.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.comboBoxFrom.FormattingEnabled = true; + this.comboBoxFrom.Name = "comboBoxFrom"; + // + // labelFrom + // + resources.ApplyResources(this.labelFrom, "labelFrom"); + this.labelFrom.Name = "labelFrom"; + // + // TransferDialog + // + resources.ApplyResources(this, "$this"); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.button4); + this.Controls.Add(this.button3); + this.Controls.Add(this.groupBox3); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.Name = "TransferDialog"; + this.ShowInTaskbar = false; + this.groupBox3.ResumeLayout(false); + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + private System.Windows.Forms.GroupBox groupBox3; + private System.Windows.Forms.Button button4; + private System.Windows.Forms.Button button3; + private TxOutListBox txOutListBox1; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.ComboBox comboBoxFrom; + private System.Windows.Forms.Label labelFrom; + } +} diff --git a/src/Neo.GUI/GUI/TransferDialog.cs b/src/Neo.GUI/GUI/TransferDialog.cs new file mode 100644 index 0000000000..357dacb04a --- /dev/null +++ b/src/Neo.GUI/GUI/TransferDialog.cs @@ -0,0 +1,42 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TransferDialog.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Network.P2P.Payloads; +using Neo.SmartContract; +using Neo.Wallets; +using System; +using System.Linq; +using System.Windows.Forms; +using static Neo.Program; + +namespace Neo.GUI +{ + public partial class TransferDialog : Form + { + public TransferDialog() + { + InitializeComponent(); + comboBoxFrom.Items.AddRange(Service.CurrentWallet.GetAccounts().Where(p => !p.WatchOnly).Select(p => p.Address).ToArray()); + } + + public Transaction GetTransaction() + { + TransferOutput[] outputs = txOutListBox1.Items.ToArray(); + UInt160 from = comboBoxFrom.SelectedItem is null ? null : ((string)comboBoxFrom.SelectedItem).ToScriptHash(Service.NeoSystem.Settings.AddressVersion); + return Service.CurrentWallet.MakeTransaction(Service.NeoSystem.StoreView, outputs, from); + } + + private void txOutListBox1_ItemsChanged(object sender, EventArgs e) + { + button3.Enabled = txOutListBox1.ItemCount > 0; + } + } +} diff --git a/src/Neo.GUI/GUI/TransferDialog.es-ES.resx b/src/Neo.GUI/GUI/TransferDialog.es-ES.resx new file mode 100644 index 0000000000..662fc871c4 --- /dev/null +++ b/src/Neo.GUI/GUI/TransferDialog.es-ES.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Lista de destinatarios + + + Cancelar + + + Aceptar + + + Transferir + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/TransferDialog.resx b/src/Neo.GUI/GUI/TransferDialog.resx new file mode 100644 index 0000000000..f902756230 --- /dev/null +++ b/src/Neo.GUI/GUI/TransferDialog.resx @@ -0,0 +1,369 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Top, Left, Right + + + Top, Bottom, Left, Right + + + + Microsoft YaHei UI, 9pt + + + 6, 24 + + + 3, 4, 3, 4 + + + 551, 276 + + + + 0 + + + txOutListBox1 + + + Neo.UI.TxOutListBox, neo-gui, Version=2.10.7263.32482, Culture=neutral, PublicKeyToken=null + + + groupBox3 + + + 0 + + + 12, 13 + + + 3, 4, 3, 4 + + + 3, 4, 3, 4 + + + 563, 308 + + + 0 + + + Recipient List + + + groupBox3 + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 3 + + + Bottom, Right + + + NoControl + + + 500, 401 + + + 3, 4, 3, 4 + + + 75, 24 + + + 2 + + + Cancel + + + button4 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 1 + + + Bottom, Right + + + False + + + NoControl + + + 419, 401 + + + 3, 4, 3, 4 + + + 75, 24 + + + 1 + + + OK + + + button3 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 2 + + + Top, Left, Right + + + Top, Left, Right + + + 78, 22 + + + 418, 0 + + + 479, 25 + + + 2 + + + comboBoxFrom + + + System.Windows.Forms.ComboBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox1 + + + 0 + + + True + + + NoControl + + + 31, 25 + + + 41, 17 + + + 4 + + + From: + + + MiddleLeft + + + labelFrom + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox1 + + + 1 + + + 12, 328 + + + 563, 60 + + + 4 + + + Advanced + + + groupBox1 + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 0 + + + True + + + 7, 17 + + + 587, 440 + + + Microsoft YaHei UI, 9pt + + + 3, 4, 3, 4 + + + CenterScreen + + + Transfer + + + TransferDialog + + + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/TransferDialog.zh-Hans.resx b/src/Neo.GUI/GUI/TransferDialog.zh-Hans.resx new file mode 100644 index 0000000000..33ddb97439 --- /dev/null +++ b/src/Neo.GUI/GUI/TransferDialog.zh-Hans.resx @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 收款人列表 + + + 取消 + + + 确定 + + + 高级 + + + + 44, 17 + + + 转自: + + + 转账 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/TxOutListBox.Designer.cs b/src/Neo.GUI/GUI/TxOutListBox.Designer.cs new file mode 100644 index 0000000000..df23b5b8c0 --- /dev/null +++ b/src/Neo.GUI/GUI/TxOutListBox.Designer.cs @@ -0,0 +1,109 @@ +// Copyright (C) 2016-2024 The Neo Project. +// +// The neo-gui is free software distributed under the MIT software +// license, see the accompanying file LICENSE in the main directory of +// the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.GUI +{ + partial class TxOutListBox + { + /// + /// 必需的设计器变量。 + /// + private System.ComponentModel.IContainer components = null; + + /// + /// 清理所有正在使用的资源。 + /// + /// 如果应释放托管资源,为 true;否则为 false。 + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region 组件设计器生成的代码 + + /// + /// 设计器支持所需的方法 - 不要修改 + /// 使用代码编辑器修改此方法的内容。 + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TxOutListBox)); + this.listBox1 = new System.Windows.Forms.ListBox(); + this.panel1 = new System.Windows.Forms.Panel(); + this.button1 = new System.Windows.Forms.Button(); + this.button2 = new System.Windows.Forms.Button(); + this.button3 = new System.Windows.Forms.Button(); + this.panel1.SuspendLayout(); + this.SuspendLayout(); + // + // listBox1 + // + resources.ApplyResources(this.listBox1, "listBox1"); + this.listBox1.Name = "listBox1"; + this.listBox1.SelectionMode = System.Windows.Forms.SelectionMode.MultiExtended; + this.listBox1.SelectedIndexChanged += new System.EventHandler(this.listBox1_SelectedIndexChanged); + // + // panel1 + // + resources.ApplyResources(this.panel1, "panel1"); + this.panel1.Controls.Add(this.button1); + this.panel1.Controls.Add(this.button2); + this.panel1.Controls.Add(this.button3); + this.panel1.Name = "panel1"; + // + // button1 + // + resources.ApplyResources(this.button1, "button1"); + this.button1.Image = global::Neo.Properties.Resources.add; + this.button1.Name = "button1"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // button2 + // + resources.ApplyResources(this.button2, "button2"); + this.button2.Image = global::Neo.Properties.Resources.remove; + this.button2.Name = "button2"; + this.button2.UseVisualStyleBackColor = true; + this.button2.Click += new System.EventHandler(this.button2_Click); + // + // button3 + // + resources.ApplyResources(this.button3, "button3"); + this.button3.Image = global::Neo.Properties.Resources.add2; + this.button3.Name = "button3"; + this.button3.UseVisualStyleBackColor = true; + this.button3.Click += new System.EventHandler(this.button3_Click); + // + // TxOutListBox + // + resources.ApplyResources(this, "$this"); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.panel1); + this.Controls.Add(this.listBox1); + this.Name = "TxOutListBox"; + this.panel1.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.ListBox listBox1; + private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.Button button2; + private System.Windows.Forms.Button button3; + } +} diff --git a/src/Neo.GUI/GUI/TxOutListBox.cs b/src/Neo.GUI/GUI/TxOutListBox.cs new file mode 100644 index 0000000000..d2e314ecaf --- /dev/null +++ b/src/Neo.GUI/GUI/TxOutListBox.cs @@ -0,0 +1,103 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TxOutListBox.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Wallets; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Windows.Forms; + +namespace Neo.GUI +{ + [DefaultEvent(nameof(ItemsChanged))] + internal partial class TxOutListBox : UserControl + { + public event EventHandler ItemsChanged; + + public AssetDescriptor Asset { get; set; } + + public int ItemCount => listBox1.Items.Count; + + public IEnumerable Items => listBox1.Items.OfType(); + + public bool ReadOnly + { + get + { + return !panel1.Enabled; + } + set + { + panel1.Enabled = !value; + } + } + + private UInt160 _script_hash = null; + public UInt160 ScriptHash + { + get + { + return _script_hash; + } + set + { + _script_hash = value; + button3.Enabled = value == null; + } + } + + public TxOutListBox() + { + InitializeComponent(); + } + + public void Clear() + { + if (listBox1.Items.Count > 0) + { + listBox1.Items.Clear(); + button2.Enabled = false; + ItemsChanged?.Invoke(this, EventArgs.Empty); + } + } + + private void listBox1_SelectedIndexChanged(object sender, EventArgs e) + { + button2.Enabled = listBox1.SelectedIndices.Count > 0; + } + + private void button1_Click(object sender, EventArgs e) + { + using PayToDialog dialog = new PayToDialog(asset: Asset, scriptHash: ScriptHash); + if (dialog.ShowDialog() != DialogResult.OK) return; + listBox1.Items.Add(dialog.GetOutput()); + ItemsChanged?.Invoke(this, EventArgs.Empty); + } + + private void button2_Click(object sender, EventArgs e) + { + while (listBox1.SelectedIndices.Count > 0) + { + listBox1.Items.RemoveAt(listBox1.SelectedIndices[0]); + } + ItemsChanged?.Invoke(this, EventArgs.Empty); + } + + private void button3_Click(object sender, EventArgs e) + { + using BulkPayDialog dialog = new BulkPayDialog(Asset); + if (dialog.ShowDialog() != DialogResult.OK) return; + listBox1.Items.AddRange(dialog.GetOutputs()); + ItemsChanged?.Invoke(this, EventArgs.Empty); + } + } +} diff --git a/src/Neo.GUI/GUI/TxOutListBox.resx b/src/Neo.GUI/GUI/TxOutListBox.resx new file mode 100644 index 0000000000..92bba21c58 --- /dev/null +++ b/src/Neo.GUI/GUI/TxOutListBox.resx @@ -0,0 +1,300 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Top, Bottom, Left, Right + + + + True + + + False + + + 17 + + + + 0, 0 + + + 3, 4, 3, 4 + + + True + + + 349, 167 + + + 0 + + + listBox1 + + + System.Windows.Forms.ListBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 1 + + + Bottom, Left, Right + + + Bottom, Left + + + NoControl + + + 0, 0 + + + 3, 4, 3, 4 + + + 27, 27 + + + 0 + + + button1 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + panel1 + + + 0 + + + Bottom, Left + + + False + + + NoControl + + + 33, 0 + + + 3, 4, 3, 4 + + + 27, 27 + + + 1 + + + button2 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + panel1 + + + 1 + + + Bottom, Left + + + NoControl + + + 66, 0 + + + 3, 4, 3, 4 + + + 27, 27 + + + 2 + + + button3 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + panel1 + + + 2 + + + 0, 175 + + + 349, 27 + + + 1 + + + panel1 + + + System.Windows.Forms.Panel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 0 + + + True + + + 7, 17 + + + 微软雅黑, 9pt + + + 3, 4, 3, 4 + + + 349, 202 + + + TxOutListBox + + + System.Windows.Forms.UserControl, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/TxOutListBoxItem.cs b/src/Neo.GUI/GUI/TxOutListBoxItem.cs new file mode 100644 index 0000000000..dfbb431ec2 --- /dev/null +++ b/src/Neo.GUI/GUI/TxOutListBoxItem.cs @@ -0,0 +1,25 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TxOutListBoxItem.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Wallets; + +namespace Neo.GUI +{ + internal class TxOutListBoxItem : TransferOutput + { + public string AssetName; + + public override string ToString() + { + return $"{ScriptHash.ToAddress(Program.Service.NeoSystem.Settings.AddressVersion)}\t{Value}\t{AssetName}"; + } + } +} diff --git a/src/Neo.GUI/GUI/UpdateDialog.Designer.cs b/src/Neo.GUI/GUI/UpdateDialog.Designer.cs new file mode 100644 index 0000000000..a443304ffa --- /dev/null +++ b/src/Neo.GUI/GUI/UpdateDialog.Designer.cs @@ -0,0 +1,149 @@ +// Copyright (C) 2016-2024 The Neo Project. +// +// The neo-gui is free software distributed under the MIT software +// license, see the accompanying file LICENSE in the main directory of +// the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.GUI +{ + partial class UpdateDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(UpdateDialog)); + this.label1 = new System.Windows.Forms.Label(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.linkLabel1 = new System.Windows.Forms.LinkLabel(); + this.button1 = new System.Windows.Forms.Button(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.textBox2 = new System.Windows.Forms.TextBox(); + this.button2 = new System.Windows.Forms.Button(); + this.linkLabel2 = new System.Windows.Forms.LinkLabel(); + this.progressBar1 = new System.Windows.Forms.ProgressBar(); + this.groupBox1.SuspendLayout(); + this.SuspendLayout(); + // + // label1 + // + resources.ApplyResources(this.label1, "label1"); + this.label1.Name = "label1"; + // + // textBox1 + // + resources.ApplyResources(this.textBox1, "textBox1"); + this.textBox1.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.textBox1.Name = "textBox1"; + this.textBox1.ReadOnly = true; + // + // linkLabel1 + // + resources.ApplyResources(this.linkLabel1, "linkLabel1"); + this.linkLabel1.Name = "linkLabel1"; + this.linkLabel1.TabStop = true; + this.linkLabel1.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkLabel1_LinkClicked); + // + // button1 + // + resources.ApplyResources(this.button1, "button1"); + this.button1.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.button1.Name = "button1"; + this.button1.UseVisualStyleBackColor = true; + // + // groupBox1 + // + resources.ApplyResources(this.groupBox1, "groupBox1"); + this.groupBox1.Controls.Add(this.textBox2); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.TabStop = false; + // + // textBox2 + // + resources.ApplyResources(this.textBox2, "textBox2"); + this.textBox2.Name = "textBox2"; + this.textBox2.ReadOnly = true; + // + // button2 + // + resources.ApplyResources(this.button2, "button2"); + this.button2.Name = "button2"; + this.button2.UseVisualStyleBackColor = true; + this.button2.Click += new System.EventHandler(this.button2_Click); + // + // linkLabel2 + // + resources.ApplyResources(this.linkLabel2, "linkLabel2"); + this.linkLabel2.Name = "linkLabel2"; + this.linkLabel2.TabStop = true; + this.linkLabel2.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkLabel2_LinkClicked); + // + // progressBar1 + // + resources.ApplyResources(this.progressBar1, "progressBar1"); + this.progressBar1.Name = "progressBar1"; + // + // UpdateDialog + // + resources.ApplyResources(this, "$this"); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.button1; + this.Controls.Add(this.progressBar1); + this.Controls.Add(this.linkLabel2); + this.Controls.Add(this.button2); + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.button1); + this.Controls.Add(this.linkLabel1); + this.Controls.Add(this.textBox1); + this.Controls.Add(this.label1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "UpdateDialog"; + this.ShowInTaskbar = false; + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.LinkLabel linkLabel1; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.TextBox textBox2; + private System.Windows.Forms.Button button2; + private System.Windows.Forms.LinkLabel linkLabel2; + private System.Windows.Forms.ProgressBar progressBar1; + } +} diff --git a/src/Neo.GUI/GUI/UpdateDialog.cs b/src/Neo.GUI/GUI/UpdateDialog.cs new file mode 100644 index 0000000000..215d19d855 --- /dev/null +++ b/src/Neo.GUI/GUI/UpdateDialog.cs @@ -0,0 +1,77 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UpdateDialog.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Properties; +using System; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Net.Http; +using System.Windows.Forms; +using System.Xml.Linq; + +namespace Neo.GUI +{ + internal partial class UpdateDialog : Form + { + private readonly HttpClient http = new(); + private readonly string download_url; + private string download_path; + + public UpdateDialog(XDocument xdoc) + { + InitializeComponent(); + Version latest = Version.Parse(xdoc.Element("update").Attribute("latest").Value); + textBox1.Text = latest.ToString(); + XElement release = xdoc.Element("update").Elements("release").First(p => p.Attribute("version").Value == latest.ToString()); + textBox2.Text = release.Element("changes").Value.Replace("\n", Environment.NewLine); + download_url = release.Attribute("file").Value; + } + + private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + Process.Start("https://neo.org/"); + } + + private void linkLabel2_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + Process.Start(download_url); + } + + private async void button2_Click(object sender, EventArgs e) + { + button1.Enabled = false; + button2.Enabled = false; + download_path = "update.zip"; + using (Stream responseStream = await http.GetStreamAsync(download_url)) + using (FileStream fileStream = new(download_path, FileMode.Create, FileAccess.Write, FileShare.None)) + { + await responseStream.CopyToAsync(fileStream); + } + DirectoryInfo di = new DirectoryInfo("update"); + if (di.Exists) di.Delete(true); + di.Create(); + ZipFile.ExtractToDirectory(download_path, di.Name); + FileSystemInfo[] fs = di.GetFileSystemInfos(); + if (fs.Length == 1 && fs[0] is DirectoryInfo directory) + { + directory.MoveTo("update2"); + di.Delete(); + Directory.Move("update2", di.Name); + } + File.WriteAllBytes("update.bat", Resources.UpdateBat); + Close(); + if (Program.MainForm != null) Program.MainForm.Close(); + Process.Start("update.bat"); + } + } +} diff --git a/src/Neo.GUI/GUI/UpdateDialog.es-ES.resx b/src/Neo.GUI/GUI/UpdateDialog.es-ES.resx new file mode 100644 index 0000000000..5f94e9b654 --- /dev/null +++ b/src/Neo.GUI/GUI/UpdateDialog.es-ES.resx @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + 94, 17 + + + Última versión: + + + 112, 15 + + + 332, 16 + + + 73, 17 + + + Web oficial + + + Cancelar + + + Registro de cambios + + + Actualizar + + + 68, 17 + + + Descargar + + + Actualización disponible + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/UpdateDialog.resx b/src/Neo.GUI/GUI/UpdateDialog.resx new file mode 100644 index 0000000000..23e774988f --- /dev/null +++ b/src/Neo.GUI/GUI/UpdateDialog.resx @@ -0,0 +1,399 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + True + + + + 12, 15 + + + 102, 17 + + + 0 + + + Newest Version: + + + label1 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 7 + + + + Top, Left, Right + + + 120, 15 + + + 324, 16 + + + 1 + + + textBox1 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 6 + + + Bottom, Left + + + True + + + 12, 335 + + + 79, 17 + + + 4 + + + Official Web + + + linkLabel1 + + + System.Windows.Forms.LinkLabel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 5 + + + Bottom, Right + + + 369, 332 + + + 75, 23 + + + 7 + + + Close + + + button1 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 4 + + + Top, Bottom, Left, Right + + + Fill + + + 3, 19 + + + True + + + Both + + + 426, 234 + + + 0 + + + False + + + textBox2 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox1 + + + 0 + + + 12, 41 + + + 432, 256 + + + 2 + + + Change logs + + + groupBox1 + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 3 + + + Bottom, Right + + + 288, 332 + + + 75, 23 + + + 6 + + + Update + + + button2 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 2 + + + Bottom, Left + + + True + + + NoControl + + + 97, 335 + + + 67, 17 + + + 5 + + + Download + + + linkLabel2 + + + System.Windows.Forms.LinkLabel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 1 + + + 12, 303 + + + 432, 23 + + + 3 + + + progressBar1 + + + System.Windows.Forms.ProgressBar, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 0 + + + True + + + 7, 17 + + + 456, 367 + + + 微软雅黑, 9pt + + + 3, 4, 3, 4 + + + CenterScreen + + + Update Available + + + UpdateDialog + + + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/UpdateDialog.zh-Hans.resx b/src/Neo.GUI/GUI/UpdateDialog.zh-Hans.resx new file mode 100644 index 0000000000..6db88c3a6e --- /dev/null +++ b/src/Neo.GUI/GUI/UpdateDialog.zh-Hans.resx @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + 59, 17 + + + 最新版本: + + + 77, 15 + + + 367, 16 + + + 32, 17 + + + 官网 + + + 关闭 + + + 更新日志 + + + 更新 + + + 50, 335 + + + 32, 17 + + + 下载 + + + 发现新版本 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/ViewContractDialog.Designer.cs b/src/Neo.GUI/GUI/ViewContractDialog.Designer.cs new file mode 100644 index 0000000000..05706aa7ef --- /dev/null +++ b/src/Neo.GUI/GUI/ViewContractDialog.Designer.cs @@ -0,0 +1,143 @@ +// Copyright (C) 2016-2024 The Neo Project. +// +// The neo-gui is free software distributed under the MIT software +// license, see the accompanying file LICENSE in the main directory of +// the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.GUI +{ + partial class ViewContractDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ViewContractDialog)); + this.label1 = new System.Windows.Forms.Label(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.label2 = new System.Windows.Forms.Label(); + this.textBox2 = new System.Windows.Forms.TextBox(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.textBox4 = new System.Windows.Forms.TextBox(); + this.button1 = new System.Windows.Forms.Button(); + this.label3 = new System.Windows.Forms.Label(); + this.textBox3 = new System.Windows.Forms.TextBox(); + this.groupBox1.SuspendLayout(); + this.SuspendLayout(); + // + // label1 + // + resources.ApplyResources(this.label1, "label1"); + this.label1.Name = "label1"; + // + // textBox1 + // + resources.ApplyResources(this.textBox1, "textBox1"); + this.textBox1.Name = "textBox1"; + this.textBox1.ReadOnly = true; + // + // label2 + // + resources.ApplyResources(this.label2, "label2"); + this.label2.Name = "label2"; + // + // textBox2 + // + resources.ApplyResources(this.textBox2, "textBox2"); + this.textBox2.Name = "textBox2"; + this.textBox2.ReadOnly = true; + // + // groupBox1 + // + resources.ApplyResources(this.groupBox1, "groupBox1"); + this.groupBox1.Controls.Add(this.textBox4); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.TabStop = false; + // + // textBox4 + // + resources.ApplyResources(this.textBox4, "textBox4"); + this.textBox4.Name = "textBox4"; + this.textBox4.ReadOnly = true; + // + // button1 + // + resources.ApplyResources(this.button1, "button1"); + this.button1.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.button1.Name = "button1"; + this.button1.UseVisualStyleBackColor = true; + // + // label3 + // + resources.ApplyResources(this.label3, "label3"); + this.label3.Name = "label3"; + // + // textBox3 + // + resources.ApplyResources(this.textBox3, "textBox3"); + this.textBox3.Name = "textBox3"; + this.textBox3.ReadOnly = true; + // + // ViewContractDialog + // + resources.ApplyResources(this, "$this"); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.button1; + this.Controls.Add(this.textBox3); + this.Controls.Add(this.label3); + this.Controls.Add(this.button1); + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.textBox2); + this.Controls.Add(this.label2); + this.Controls.Add(this.textBox1); + this.Controls.Add(this.label1); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "ViewContractDialog"; + this.ShowInTaskbar = false; + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.TextBox textBox2; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.TextBox textBox4; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.TextBox textBox3; + } +} diff --git a/src/Neo.GUI/GUI/ViewContractDialog.cs b/src/Neo.GUI/GUI/ViewContractDialog.cs new file mode 100644 index 0000000000..00ea963328 --- /dev/null +++ b/src/Neo.GUI/GUI/ViewContractDialog.cs @@ -0,0 +1,30 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ViewContractDialog.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.SmartContract; +using Neo.Wallets; +using System.Linq; +using System.Windows.Forms; + +namespace Neo.GUI +{ + public partial class ViewContractDialog : Form + { + public ViewContractDialog(Contract contract) + { + InitializeComponent(); + textBox1.Text = contract.ScriptHash.ToAddress(Program.Service.NeoSystem.Settings.AddressVersion); + textBox2.Text = contract.ScriptHash.ToString(); + textBox3.Text = contract.ParameterList.Cast().ToArray().ToHexString(); + textBox4.Text = contract.Script.ToHexString(); + } + } +} diff --git a/src/Neo.GUI/GUI/ViewContractDialog.es-ES.resx b/src/Neo.GUI/GUI/ViewContractDialog.es-ES.resx new file mode 100644 index 0000000000..c792cfc6ab --- /dev/null +++ b/src/Neo.GUI/GUI/ViewContractDialog.es-ES.resx @@ -0,0 +1,187 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + 72, 15 + + + 65, 17 + + + Dirección: + + + 143, 12 + + + 363, 23 + + + 39, 44 + + + 98, 17 + + + Hash del script: + + + 143, 41 + + + 363, 23 + + + 494, 273 + + + Código del script + + + 488, 251 + + + 431, 378 + + + Cancelar + + + 9, 73 + + + 128, 17 + + + Lista de parámetros: + + + 143, 70 + + + 363, 23 + + + 518, 413 + + + Ver contrato + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/ViewContractDialog.resx b/src/Neo.GUI/GUI/ViewContractDialog.resx new file mode 100644 index 0000000000..97c2f61083 --- /dev/null +++ b/src/Neo.GUI/GUI/ViewContractDialog.resx @@ -0,0 +1,387 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + True + + + + 47, 15 + + + 59, 17 + + + 0 + + + Address: + + + label1 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 7 + + + + Top, Left, Right + + + 112, 12 + + + 328, 23 + + + 1 + + + textBox1 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 6 + + + True + + + 29, 44 + + + 77, 17 + + + 2 + + + Script Hash: + + + label2 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 5 + + + Top, Left, Right + + + 112, 41 + + + 328, 23 + + + 3 + + + textBox2 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 4 + + + Top, Bottom, Left, Right + + + Fill + + + 3, 19 + + + True + + + Vertical + + + 422, 251 + + + 0 + + + textBox4 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox1 + + + 0 + + + 12, 99 + + + 428, 273 + + + 6 + + + Redeem Script + + + groupBox1 + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 3 + + + Bottom, Right + + + 365, 378 + + + 75, 23 + + + 7 + + + Close + + + button1 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 2 + + + True + + + 12, 73 + + + 94, 17 + + + 4 + + + Parameter List: + + + label3 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 1 + + + Top, Left, Right + + + 112, 70 + + + 328, 23 + + + 5 + + + textBox3 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 0 + + + True + + + 7, 17 + + + 452, 413 + + + Microsoft YaHei UI, 9pt + + + 2, 2, 2, 2 + + + CenterScreen + + + View Contract + + + ViewContractDialog + + + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/ViewContractDialog.zh-Hans.resx b/src/Neo.GUI/GUI/ViewContractDialog.zh-Hans.resx new file mode 100644 index 0000000000..9a870a5476 --- /dev/null +++ b/src/Neo.GUI/GUI/ViewContractDialog.zh-Hans.resx @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + 36, 15 + + + 35, 17 + + + 地址: + + + 77, 12 + + + 363, 23 + + + 12, 44 + + + 59, 17 + + + 脚本散列: + + + 77, 41 + + + 363, 23 + + + 脚本 + + + 关闭 + + + 59, 17 + + + 形参列表: + + + 77, 70 + + + 363, 23 + + + 查看合约 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/ViewPrivateKeyDialog.cs b/src/Neo.GUI/GUI/ViewPrivateKeyDialog.cs new file mode 100644 index 0000000000..7beaa56271 --- /dev/null +++ b/src/Neo.GUI/GUI/ViewPrivateKeyDialog.cs @@ -0,0 +1,29 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ViewPrivateKeyDialog.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Wallets; +using System.Windows.Forms; + +namespace Neo.GUI +{ + internal partial class ViewPrivateKeyDialog : Form + { + public ViewPrivateKeyDialog(WalletAccount account) + { + InitializeComponent(); + KeyPair key = account.GetKey(); + textBox3.Text = account.Address; + textBox4.Text = key.PublicKey.EncodePoint(true).ToHexString(); + textBox1.Text = key.PrivateKey.ToHexString(); + textBox2.Text = key.Export(); + } + } +} diff --git a/src/Neo.GUI/GUI/ViewPrivateKeyDialog.designer.cs b/src/Neo.GUI/GUI/ViewPrivateKeyDialog.designer.cs new file mode 100644 index 0000000000..306904a85e --- /dev/null +++ b/src/Neo.GUI/GUI/ViewPrivateKeyDialog.designer.cs @@ -0,0 +1,155 @@ +// Copyright (C) 2016-2024 The Neo Project. +// +// The neo-gui is free software distributed under the MIT software +// license, see the accompanying file LICENSE in the main directory of +// the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.GUI +{ + partial class ViewPrivateKeyDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ViewPrivateKeyDialog)); + this.label1 = new System.Windows.Forms.Label(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.textBox2 = new System.Windows.Forms.TextBox(); + this.label4 = new System.Windows.Forms.Label(); + this.label3 = new System.Windows.Forms.Label(); + this.button1 = new System.Windows.Forms.Button(); + this.textBox3 = new System.Windows.Forms.TextBox(); + this.label2 = new System.Windows.Forms.Label(); + this.textBox4 = new System.Windows.Forms.TextBox(); + this.groupBox1.SuspendLayout(); + this.SuspendLayout(); + // + // label1 + // + resources.ApplyResources(this.label1, "label1"); + this.label1.Name = "label1"; + // + // textBox1 + // + resources.ApplyResources(this.textBox1, "textBox1"); + this.textBox1.Name = "textBox1"; + this.textBox1.ReadOnly = true; + // + // groupBox1 + // + resources.ApplyResources(this.groupBox1, "groupBox1"); + this.groupBox1.Controls.Add(this.textBox2); + this.groupBox1.Controls.Add(this.label4); + this.groupBox1.Controls.Add(this.label3); + this.groupBox1.Controls.Add(this.textBox1); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.TabStop = false; + // + // textBox2 + // + resources.ApplyResources(this.textBox2, "textBox2"); + this.textBox2.Name = "textBox2"; + this.textBox2.ReadOnly = true; + // + // label4 + // + resources.ApplyResources(this.label4, "label4"); + this.label4.Name = "label4"; + // + // label3 + // + resources.ApplyResources(this.label3, "label3"); + this.label3.Name = "label3"; + // + // button1 + // + resources.ApplyResources(this.button1, "button1"); + this.button1.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.button1.Name = "button1"; + this.button1.UseVisualStyleBackColor = true; + // + // textBox3 + // + resources.ApplyResources(this.textBox3, "textBox3"); + this.textBox3.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.textBox3.Name = "textBox3"; + this.textBox3.ReadOnly = true; + // + // label2 + // + resources.ApplyResources(this.label2, "label2"); + this.label2.Name = "label2"; + // + // textBox4 + // + resources.ApplyResources(this.textBox4, "textBox4"); + this.textBox4.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.textBox4.Name = "textBox4"; + this.textBox4.ReadOnly = true; + // + // ViewPrivateKeyDialog + // + this.AcceptButton = this.button1; + resources.ApplyResources(this, "$this"); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.button1; + this.Controls.Add(this.textBox4); + this.Controls.Add(this.label2); + this.Controls.Add(this.textBox3); + this.Controls.Add(this.button1); + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.label1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "ViewPrivateKeyDialog"; + this.ShowInTaskbar = false; + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox textBox1; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.TextBox textBox2; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.TextBox textBox3; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.TextBox textBox4; + } +} diff --git a/src/Neo.GUI/GUI/ViewPrivateKeyDialog.es-ES.resx b/src/Neo.GUI/GUI/ViewPrivateKeyDialog.es-ES.resx new file mode 100644 index 0000000000..4c3e507b35 --- /dev/null +++ b/src/Neo.GUI/GUI/ViewPrivateKeyDialog.es-ES.resx @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + 32, 11 + + + 65, 17 + + + Dirección: + + + Clave privada + + + Cerrar + + + 100, 11 + + + 470, 16 + + + 9, 36 + + + 88, 17 + + + Clave pública: + + + 100, 36 + + + 470, 16 + + + Ver clave pública + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/ViewPrivateKeyDialog.resx b/src/Neo.GUI/GUI/ViewPrivateKeyDialog.resx new file mode 100644 index 0000000000..49368fb3f2 --- /dev/null +++ b/src/Neo.GUI/GUI/ViewPrivateKeyDialog.resx @@ -0,0 +1,408 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + True + + + + 29, 11 + + + 59, 17 + + + 0 + + + Address: + + + label1 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 5 + + + + Top, Left, Right + + + 47, 22 + + + 494, 23 + + + 1 + + + textBox1 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox1 + + + 3 + + + Top, Left, Right + + + Top, Left, Right + + + 47, 51 + + + 494, 23 + + + 3 + + + textBox2 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox1 + + + 0 + + + True + + + 8, 54 + + + 33, 17 + + + 2 + + + WIF: + + + label4 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox1 + + + 1 + + + True + + + 6, 25 + + + 35, 17 + + + 0 + + + HEX: + + + label3 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox1 + + + 2 + + + 12, 73 + + + 558, 93 + + + 2 + + + Private Key + + + groupBox1 + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 4 + + + Bottom, Right + + + 495, 172 + + + 75, 23 + + + 3 + + + close + + + button1 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 3 + + + Top, Left, Right + + + 94, 11 + + + 476, 16 + + + 4 + + + textBox3 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 2 + + + True + + + 18, 36 + + + 70, 17 + + + 5 + + + Public Key: + + + label2 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 1 + + + Top, Left, Right + + + 94, 36 + + + 476, 16 + + + 6 + + + textBox4 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 0 + + + True + + + 7, 17 + + + 582, 207 + + + 微软雅黑, 9pt + + + 3, 4, 3, 4 + + + CenterScreen + + + View Private Key + + + ViewPrivateKeyDialog + + + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/ViewPrivateKeyDialog.zh-Hans.resx b/src/Neo.GUI/GUI/ViewPrivateKeyDialog.zh-Hans.resx new file mode 100644 index 0000000000..aac178a792 --- /dev/null +++ b/src/Neo.GUI/GUI/ViewPrivateKeyDialog.zh-Hans.resx @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + 18, 9 + + + 35, 17 + + + 地址: + + + 487, 23 + + + 487, 23 + + + 12, 53 + + + 540, 85 + + + 私钥 + + + 477, 153 + + + 关闭 + + + 59, 9 + + + 493, 16 + + + 18, 31 + + + 35, 17 + + + 公钥: + + + 59, 31 + + + 493, 16 + + + 564, 188 + + + 查看私钥 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/VotingDialog.Designer.cs b/src/Neo.GUI/GUI/VotingDialog.Designer.cs new file mode 100644 index 0000000000..706b1a5bbf --- /dev/null +++ b/src/Neo.GUI/GUI/VotingDialog.Designer.cs @@ -0,0 +1,111 @@ +// Copyright (C) 2016-2024 The Neo Project. +// +// The neo-gui is free software distributed under the MIT software +// license, see the accompanying file LICENSE in the main directory of +// the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.GUI +{ + partial class VotingDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(VotingDialog)); + this.label1 = new System.Windows.Forms.Label(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.textBox1 = new System.Windows.Forms.TextBox(); + this.button1 = new System.Windows.Forms.Button(); + this.button2 = new System.Windows.Forms.Button(); + this.groupBox1.SuspendLayout(); + this.SuspendLayout(); + // + // label1 + // + resources.ApplyResources(this.label1, "label1"); + this.label1.Name = "label1"; + // + // groupBox1 + // + resources.ApplyResources(this.groupBox1, "groupBox1"); + this.groupBox1.Controls.Add(this.textBox1); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.TabStop = false; + // + // textBox1 + // + resources.ApplyResources(this.textBox1, "textBox1"); + this.textBox1.AcceptsReturn = true; + this.textBox1.Name = "textBox1"; + // + // button1 + // + resources.ApplyResources(this.button1, "button1"); + this.button1.DialogResult = System.Windows.Forms.DialogResult.OK; + this.button1.Name = "button1"; + this.button1.UseVisualStyleBackColor = true; + // + // button2 + // + resources.ApplyResources(this.button2, "button2"); + this.button2.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.button2.Name = "button2"; + this.button2.UseVisualStyleBackColor = true; + // + // VotingDialog + // + resources.ApplyResources(this, "$this"); + this.AcceptButton = this.button1; + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.button2; + this.Controls.Add(this.button2); + this.Controls.Add(this.button1); + this.Controls.Add(this.groupBox1); + this.Controls.Add(this.label1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "VotingDialog"; + this.ShowInTaskbar = false; + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Label label1; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.Button button1; + private System.Windows.Forms.Button button2; + private System.Windows.Forms.TextBox textBox1; + } +} diff --git a/src/Neo.GUI/GUI/VotingDialog.cs b/src/Neo.GUI/GUI/VotingDialog.cs new file mode 100644 index 0000000000..d289cc48ca --- /dev/null +++ b/src/Neo.GUI/GUI/VotingDialog.cs @@ -0,0 +1,54 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// VotingDialog.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Cryptography.ECC; +using Neo.IO; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using System.Linq; +using System.Windows.Forms; + +namespace Neo.GUI +{ + internal partial class VotingDialog : Form + { + private readonly UInt160 script_hash; + + public byte[] GetScript() + { + ECPoint[] pubkeys = textBox1.Lines.Select(p => ECPoint.Parse(p, ECCurve.Secp256r1)).ToArray(); + using ScriptBuilder sb = new ScriptBuilder(); + sb.EmitDynamicCall(NativeContract.NEO.Hash, "vote", new ContractParameter + { + Type = ContractParameterType.Hash160, + Value = script_hash + }, new ContractParameter + { + Type = ContractParameterType.Array, + Value = pubkeys.Select(p => new ContractParameter + { + Type = ContractParameterType.PublicKey, + Value = p + }).ToArray() + }); + return sb.ToArray(); + } + + public VotingDialog(UInt160 script_hash) + { + InitializeComponent(); + this.script_hash = script_hash; + label1.Text = script_hash.ToAddress(Program.Service.NeoSystem.Settings.AddressVersion); + } + } +} diff --git a/src/Neo.GUI/GUI/VotingDialog.es-ES.resx b/src/Neo.GUI/GUI/VotingDialog.es-ES.resx new file mode 100644 index 0000000000..e2afed46e3 --- /dev/null +++ b/src/Neo.GUI/GUI/VotingDialog.es-ES.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Candidatos + + + Aceptar + + + Cancelar + + + Votación + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/VotingDialog.resx b/src/Neo.GUI/GUI/VotingDialog.resx new file mode 100644 index 0000000000..2416392148 --- /dev/null +++ b/src/Neo.GUI/GUI/VotingDialog.resx @@ -0,0 +1,300 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Top, Left, Right + + + + 微软雅黑, 11pt + + + 12, 23 + + + 562, 39 + + + + 0 + + + label1 + + + MiddleCenter + + + label1 + + + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 3 + + + Top, Bottom, Left, Right + + + Fill + + + Lucida Console, 9pt + + + 3, 19 + + + True + + + Vertical + + + 556, 368 + + + 0 + + + False + + + textBox1 + + + System.Windows.Forms.TextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + groupBox1 + + + 0 + + + 12, 65 + + + 562, 390 + + + 1 + + + Candidates + + + groupBox1 + + + System.Windows.Forms.GroupBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 2 + + + Bottom, Right + + + 418, 461 + + + 75, 23 + + + 2 + + + OK + + + button1 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 1 + + + Bottom, Right + + + 499, 461 + + + 75, 23 + + + 3 + + + Cancel + + + button2 + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + + 0 + + + True + + + 7, 17 + + + 586, 496 + + + 微软雅黑, 9pt + + + 3, 4, 3, 4 + + + CenterScreen + + + Voting + + + VotingDialog + + + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/VotingDialog.zh-Hans.resx b/src/Neo.GUI/GUI/VotingDialog.zh-Hans.resx new file mode 100644 index 0000000000..e41916cae4 --- /dev/null +++ b/src/Neo.GUI/GUI/VotingDialog.zh-Hans.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 候选人 + + + 确定 + + + 取消 + + + 投票 + + \ No newline at end of file diff --git a/src/Neo.GUI/GUI/Wrappers/HexConverter.cs b/src/Neo.GUI/GUI/Wrappers/HexConverter.cs new file mode 100644 index 0000000000..757bfd3b97 --- /dev/null +++ b/src/Neo.GUI/GUI/Wrappers/HexConverter.cs @@ -0,0 +1,49 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// HexConverter.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.ComponentModel; +using System.Globalization; + +namespace Neo.GUI.Wrappers +{ + internal class HexConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + if (sourceType == typeof(string)) + return true; + return false; + } + + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { + if (destinationType == typeof(string)) + return true; + return false; + } + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + if (value is string s) + return s.HexToBytes(); + throw new NotSupportedException(); + } + + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + if (destinationType != typeof(string)) + throw new NotSupportedException(); + if (!(value is byte[] array)) return null; + return array.ToHexString(); + } + } +} diff --git a/src/Neo.GUI/GUI/Wrappers/ScriptEditor.cs b/src/Neo.GUI/GUI/Wrappers/ScriptEditor.cs new file mode 100644 index 0000000000..20bc894786 --- /dev/null +++ b/src/Neo.GUI/GUI/Wrappers/ScriptEditor.cs @@ -0,0 +1,36 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ScriptEditor.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.ComponentModel; +using System.IO; +using System.Windows.Forms; +using System.Windows.Forms.Design; + +namespace Neo.GUI.Wrappers +{ + internal class ScriptEditor : FileNameEditor + { + public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) + { + string path = (string)base.EditValue(context, provider, null); + if (path == null) return null; + return File.ReadAllBytes(path); + } + + protected override void InitializeDialog(OpenFileDialog openFileDialog) + { + base.InitializeDialog(openFileDialog); + openFileDialog.DefaultExt = "avm"; + openFileDialog.Filter = "NeoContract|*.avm"; + } + } +} diff --git a/src/Neo.GUI/GUI/Wrappers/SignerWrapper.cs b/src/Neo.GUI/GUI/Wrappers/SignerWrapper.cs new file mode 100644 index 0000000000..e022ae7746 --- /dev/null +++ b/src/Neo.GUI/GUI/Wrappers/SignerWrapper.cs @@ -0,0 +1,38 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// SignerWrapper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Cryptography.ECC; +using Neo.Network.P2P.Payloads; +using System.Collections.Generic; +using System.ComponentModel; + +namespace Neo.GUI.Wrappers +{ + internal class SignerWrapper + { + [TypeConverter(typeof(UIntBaseConverter))] + public UInt160 Account { get; set; } + public WitnessScope Scopes { get; set; } + public List AllowedContracts { get; set; } = new List(); + public List AllowedGroups { get; set; } = new List(); + + public Signer Unwrap() + { + return new Signer + { + Account = Account, + Scopes = Scopes, + AllowedContracts = AllowedContracts.ToArray(), + AllowedGroups = AllowedGroups.ToArray() + }; + } + } +} diff --git a/src/Neo.GUI/GUI/Wrappers/TransactionAttributeWrapper.cs b/src/Neo.GUI/GUI/Wrappers/TransactionAttributeWrapper.cs new file mode 100644 index 0000000000..a348eb98d1 --- /dev/null +++ b/src/Neo.GUI/GUI/Wrappers/TransactionAttributeWrapper.cs @@ -0,0 +1,30 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TransactionAttributeWrapper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using Neo.Network.P2P.Payloads; +using System.ComponentModel; + +namespace Neo.GUI.Wrappers +{ + internal class TransactionAttributeWrapper + { + public TransactionAttributeType Usage { get; set; } + [TypeConverter(typeof(HexConverter))] + public byte[] Data { get; set; } + + public TransactionAttribute Unwrap() + { + MemoryReader reader = new(Data); + return TransactionAttribute.DeserializeFrom(ref reader); + } + } +} diff --git a/src/Neo.GUI/GUI/Wrappers/TransactionWrapper.cs b/src/Neo.GUI/GUI/Wrappers/TransactionWrapper.cs new file mode 100644 index 0000000000..86e84e9c87 --- /dev/null +++ b/src/Neo.GUI/GUI/Wrappers/TransactionWrapper.cs @@ -0,0 +1,59 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TransactionWrapper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Network.P2P.Payloads; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing.Design; +using System.Linq; + +namespace Neo.GUI.Wrappers +{ + internal class TransactionWrapper + { + [Category("Basic")] + public byte Version { get; set; } + [Category("Basic")] + public uint Nonce { get; set; } + [Category("Basic")] + public List Signers { get; set; } + [Category("Basic")] + public long SystemFee { get; set; } + [Category("Basic")] + public long NetworkFee { get; set; } + [Category("Basic")] + public uint ValidUntilBlock { get; set; } + [Category("Basic")] + public List Attributes { get; set; } = new List(); + [Category("Basic")] + [Editor(typeof(ScriptEditor), typeof(UITypeEditor))] + [TypeConverter(typeof(HexConverter))] + public byte[] Script { get; set; } + [Category("Basic")] + public List Witnesses { get; set; } = new List(); + + public Transaction Unwrap() + { + return new Transaction + { + Version = Version, + Nonce = Nonce, + Signers = Signers.Select(p => p.Unwrap()).ToArray(), + SystemFee = SystemFee, + NetworkFee = NetworkFee, + ValidUntilBlock = ValidUntilBlock, + Attributes = Attributes.Select(p => p.Unwrap()).ToArray(), + Script = Script, + Witnesses = Witnesses.Select(p => p.Unwrap()).ToArray() + }; + } + } +} diff --git a/src/Neo.GUI/GUI/Wrappers/UIntBaseConverter.cs b/src/Neo.GUI/GUI/Wrappers/UIntBaseConverter.cs new file mode 100644 index 0000000000..2def7ea4ab --- /dev/null +++ b/src/Neo.GUI/GUI/Wrappers/UIntBaseConverter.cs @@ -0,0 +1,54 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UIntBaseConverter.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.ComponentModel; +using System.Globalization; + +namespace Neo.GUI.Wrappers +{ + internal class UIntBaseConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + { + if (sourceType == typeof(string)) + return true; + return false; + } + + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { + if (destinationType == typeof(string)) + return true; + return false; + } + + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + { + if (value is string s) + return context.PropertyDescriptor.PropertyType.GetMethod("Parse").Invoke(null, new[] { s }); + throw new NotSupportedException(); + } + + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + if (destinationType != typeof(string)) + throw new NotSupportedException(); + + return value switch + { + UInt160 i => i.ToString(), + UInt256 i => i.ToString(), + _ => null, + }; + } + } +} diff --git a/src/Neo.GUI/GUI/Wrappers/WitnessWrapper.cs b/src/Neo.GUI/GUI/Wrappers/WitnessWrapper.cs new file mode 100644 index 0000000000..1c78a34dd1 --- /dev/null +++ b/src/Neo.GUI/GUI/Wrappers/WitnessWrapper.cs @@ -0,0 +1,36 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// WitnessWrapper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Network.P2P.Payloads; +using System.ComponentModel; +using System.Drawing.Design; + +namespace Neo.GUI.Wrappers +{ + internal class WitnessWrapper + { + [Editor(typeof(ScriptEditor), typeof(UITypeEditor))] + [TypeConverter(typeof(HexConverter))] + public byte[] InvocationScript { get; set; } + [Editor(typeof(ScriptEditor), typeof(UITypeEditor))] + [TypeConverter(typeof(HexConverter))] + public byte[] VerificationScript { get; set; } + + public Witness Unwrap() + { + return new Witness + { + InvocationScript = InvocationScript, + VerificationScript = VerificationScript + }; + } + } +} diff --git a/src/Neo.GUI/IO/Actors/EventWrapper.cs b/src/Neo.GUI/IO/Actors/EventWrapper.cs new file mode 100644 index 0000000000..67c488dc28 --- /dev/null +++ b/src/Neo.GUI/IO/Actors/EventWrapper.cs @@ -0,0 +1,43 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// EventWrapper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Akka.Actor; +using System; + +namespace Neo.IO.Actors +{ + internal class EventWrapper : UntypedActor + { + private readonly Action callback; + + public EventWrapper(Action callback) + { + this.callback = callback; + Context.System.EventStream.Subscribe(Self, typeof(T)); + } + + protected override void OnReceive(object message) + { + if (message is T obj) callback(obj); + } + + protected override void PostStop() + { + Context.System.EventStream.Unsubscribe(Self); + base.PostStop(); + } + + public static Props Props(Action callback) + { + return Akka.Actor.Props.Create(() => new EventWrapper(callback)); + } + } +} diff --git a/src/Neo.GUI/Neo.GUI.csproj b/src/Neo.GUI/Neo.GUI.csproj new file mode 100644 index 0000000000..078434fc13 --- /dev/null +++ b/src/Neo.GUI/Neo.GUI.csproj @@ -0,0 +1,66 @@ + + + + 2016-2024 The Neo Project + Neo.GUI + WinExe + net8.0-windows + true + Neo + true + Neo.GUI + neo.ico + false + ../../bin/$(AssemblyTitle) + + + + + + + + + + + + + DeveloperToolsForm.cs + + + DeveloperToolsForm.cs + + + True + True + Resources.resx + + + True + True + Strings.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + ResXFileCodeGenerator + Strings.Designer.cs + + + Strings.resx + + + Strings.resx + + + + + + + + + diff --git a/src/Neo.GUI/Program.cs b/src/Neo.GUI/Program.cs new file mode 100644 index 0000000000..1317c4c793 --- /dev/null +++ b/src/Neo.GUI/Program.cs @@ -0,0 +1,96 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Program.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.CLI; +using Neo.GUI; +using Neo.SmartContract.Native; +using System; +using System.IO; +using System.Reflection; +using System.Windows.Forms; +using System.Xml.Linq; + +namespace Neo +{ + static class Program + { + public static MainService Service = new MainService(); + public static MainForm MainForm; + public static UInt160[] NEP5Watched = { NativeContract.NEO.Hash, NativeContract.GAS.Hash }; + + private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + using FileStream fs = new FileStream("error.log", FileMode.Create, FileAccess.Write, FileShare.None); + using StreamWriter w = new StreamWriter(fs); + if (e.ExceptionObject is Exception ex) + { + PrintErrorLogs(w, ex); + } + else + { + w.WriteLine(e.ExceptionObject.GetType()); + w.WriteLine(e.ExceptionObject); + } + } + + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main(string[] args) + { + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + Application.SetHighDpiMode(HighDpiMode.SystemAware); + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + XDocument xdoc = null; + try + { + xdoc = XDocument.Load("https://raw.githubusercontent.com/neo-project/neo-gui/master/update.xml"); + } + catch { } + if (xdoc != null) + { + Version version = Assembly.GetExecutingAssembly().GetName().Version; + Version minimum = Version.Parse(xdoc.Element("update").Attribute("minimum").Value); + if (version < minimum) + { + using UpdateDialog dialog = new UpdateDialog(xdoc); + dialog.ShowDialog(); + return; + } + } + Service.OnStartWithCommandLine(args); + Application.Run(MainForm = new MainForm(xdoc)); + Service.Stop(); + } + + private static void PrintErrorLogs(StreamWriter writer, Exception ex) + { + writer.WriteLine(ex.GetType()); + writer.WriteLine(ex.Message); + writer.WriteLine(ex.StackTrace); + if (ex is AggregateException ex2) + { + foreach (Exception inner in ex2.InnerExceptions) + { + writer.WriteLine(); + PrintErrorLogs(writer, inner); + } + } + else if (ex.InnerException != null) + { + writer.WriteLine(); + PrintErrorLogs(writer, ex.InnerException); + } + } + } +} diff --git a/src/Neo.GUI/Properties/Resources.Designer.cs b/src/Neo.GUI/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..4cbcca4fd2 --- /dev/null +++ b/src/Neo.GUI/Properties/Resources.Designer.cs @@ -0,0 +1,133 @@ +// Copyright (C) 2016-2023 The Neo Project. +// +// The neo-gui is free software distributed under the MIT software +// license, see the accompanying file LICENSE in the main directory of +// the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本:4.0.30319.42000 +// +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将会丢失。 +// +//------------------------------------------------------------------------------ + +namespace Neo.Properties { + using System; + + + /// + /// 一个强类型的资源类,用于查找本地化的字符串等。 + /// + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// 返回此类使用的缓存的 ResourceManager 实例。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Neo.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 重写当前线程的 CurrentUICulture 属性 + /// 重写当前线程的 CurrentUICulture 属性。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// 查找 System.Drawing.Bitmap 类型的本地化资源。 + /// + internal static System.Drawing.Bitmap add { + get { + object obj = ResourceManager.GetObject("add", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// 查找 System.Drawing.Bitmap 类型的本地化资源。 + /// + internal static System.Drawing.Bitmap add2 { + get { + object obj = ResourceManager.GetObject("add2", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// 查找 System.Drawing.Bitmap 类型的本地化资源。 + /// + internal static System.Drawing.Bitmap remark { + get { + object obj = ResourceManager.GetObject("remark", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// 查找 System.Drawing.Bitmap 类型的本地化资源。 + /// + internal static System.Drawing.Bitmap remove { + get { + object obj = ResourceManager.GetObject("remove", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// 查找 System.Drawing.Bitmap 类型的本地化资源。 + /// + internal static System.Drawing.Bitmap search { + get { + object obj = ResourceManager.GetObject("search", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// 查找 System.Byte[] 类型的本地化资源。 + /// + internal static byte[] UpdateBat { + get { + object obj = ResourceManager.GetObject("UpdateBat", resourceCulture); + return ((byte[])(obj)); + } + } + } +} diff --git a/src/Neo.GUI/Properties/Resources.resx b/src/Neo.GUI/Properties/Resources.resx new file mode 100644 index 0000000000..40ca55734d --- /dev/null +++ b/src/Neo.GUI/Properties/Resources.resx @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\add.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\add2.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\remark.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\remove.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\search.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\update.bat;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/Neo.GUI/Properties/Strings.Designer.cs b/src/Neo.GUI/Properties/Strings.Designer.cs new file mode 100644 index 0000000000..ceafe6ac3a --- /dev/null +++ b/src/Neo.GUI/Properties/Strings.Designer.cs @@ -0,0 +1,542 @@ +// Copyright (C) 2016-2023 The Neo Project. +// +// The neo-gui is free software distributed under the MIT software +// license, see the accompanying file LICENSE in the main directory of +// the project or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Neo.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Strings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Strings() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Neo.Properties.Strings", typeof(Strings).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to About. + /// + internal static string About { + get { + return ResourceManager.GetString("About", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to NEO. + /// + internal static string AboutMessage { + get { + return ResourceManager.GetString("AboutMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Version:. + /// + internal static string AboutVersion { + get { + return ResourceManager.GetString("AboutVersion", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to add smart contract, corresponding private key missing in this wallet.. + /// + internal static string AddContractFailedMessage { + get { + return ResourceManager.GetString("AddContractFailedMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Address. + /// + internal static string Address { + get { + return ResourceManager.GetString("Address", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cancel. + /// + internal static string Cancel { + get { + return ResourceManager.GetString("Cancel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Change password successful.. + /// + internal static string ChangePasswordSuccessful { + get { + return ResourceManager.GetString("ChangePasswordSuccessful", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Confirm. + /// + internal static string Confirm { + get { + return ResourceManager.GetString("Confirm", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to will be consumed, confirm?. + /// + internal static string CostTips { + get { + return ResourceManager.GetString("CostTips", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cost Warning. + /// + internal static string CostTitle { + get { + return ResourceManager.GetString("CostTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Confirmation. + /// + internal static string DeleteAddressConfirmationCaption { + get { + return ResourceManager.GetString("DeleteAddressConfirmationCaption", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Upon deletion, assets in these addresses will be permanently lost, are you sure to proceed?. + /// + internal static string DeleteAddressConfirmationMessage { + get { + return ResourceManager.GetString("DeleteAddressConfirmationMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Assets cannot be recovered once deleted, are you sure to delete the assets?. + /// + internal static string DeleteAssetConfirmationMessage { + get { + return ResourceManager.GetString("DeleteAssetConfirmationMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Confirmation. + /// + internal static string DeleteConfirmation { + get { + return ResourceManager.GetString("DeleteConfirmation", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enter remark here, which will be recorded on the blockchain. + /// + internal static string EnterRemarkMessage { + get { + return ResourceManager.GetString("EnterRemarkMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Transaction Remark. + /// + internal static string EnterRemarkTitle { + get { + return ResourceManager.GetString("EnterRemarkTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Execution terminated in fault state.. + /// + internal static string ExecutionFailed { + get { + return ResourceManager.GetString("ExecutionFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Expired. + /// + internal static string ExpiredCertificate { + get { + return ResourceManager.GetString("ExpiredCertificate", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed. + /// + internal static string Failed { + get { + return ResourceManager.GetString("Failed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to High Priority Transaction. + /// + internal static string HighPriority { + get { + return ResourceManager.GetString("HighPriority", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Import Watch-Only Address. + /// + internal static string ImportWatchOnlyAddress { + get { + return ResourceManager.GetString("ImportWatchOnlyAddress", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Transaction initiated, but the signature is incomplete.. + /// + internal static string IncompletedSignatureMessage { + get { + return ResourceManager.GetString("IncompletedSignatureMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Incomplete signature. + /// + internal static string IncompletedSignatureTitle { + get { + return ResourceManager.GetString("IncompletedSignatureTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You have cancelled the certificate installation.. + /// + internal static string InstallCertificateCancel { + get { + return ResourceManager.GetString("InstallCertificateCancel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Install the certificate. + /// + internal static string InstallCertificateCaption { + get { + return ResourceManager.GetString("InstallCertificateCaption", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to NEO must install Onchain root certificate to validate assets on the blockchain, install it now?. + /// + internal static string InstallCertificateText { + get { + return ResourceManager.GetString("InstallCertificateText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Insufficient funds, transaction cannot be initiated.. + /// + internal static string InsufficientFunds { + get { + return ResourceManager.GetString("InsufficientFunds", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid. + /// + internal static string InvalidCertificate { + get { + return ResourceManager.GetString("InvalidCertificate", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Migrate Wallet. + /// + internal static string MigrateWalletCaption { + get { + return ResourceManager.GetString("MigrateWalletCaption", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Opening wallet files in older versions, update to newest format? + ///Note: updated files cannot be openned by clients in older versions!. + /// + internal static string MigrateWalletMessage { + get { + return ResourceManager.GetString("MigrateWalletMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Wallet file relocated. New wallet file has been saved at: . + /// + internal static string MigrateWalletSucceedMessage { + get { + return ResourceManager.GetString("MigrateWalletSucceedMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Password Incorrect. + /// + internal static string PasswordIncorrect { + get { + return ResourceManager.GetString("PasswordIncorrect", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Data broadcast success, the hash is shown as follows:. + /// + internal static string RelaySuccessText { + get { + return ResourceManager.GetString("RelaySuccessText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Broadcast Success. + /// + internal static string RelaySuccessTitle { + get { + return ResourceManager.GetString("RelaySuccessTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Raw:. + /// + internal static string RelayTitle { + get { + return ResourceManager.GetString("RelayTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Transaction sent, TXID:. + /// + internal static string SendTxSucceedMessage { + get { + return ResourceManager.GetString("SendTxSucceedMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Transaction successful. + /// + internal static string SendTxSucceedTitle { + get { + return ResourceManager.GetString("SendTxSucceedTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The private key that can sign the data is not found.. + /// + internal static string SigningFailedKeyNotFoundMessage { + get { + return ResourceManager.GetString("SigningFailedKeyNotFoundMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You must input JSON object pending signature data.. + /// + internal static string SigningFailedNoDataMessage { + get { + return ResourceManager.GetString("SigningFailedNoDataMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to System. + /// + internal static string SystemIssuer { + get { + return ResourceManager.GetString("SystemIssuer", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Validation failed, the counterparty falsified the transaction content!. + /// + internal static string TradeFailedFakeDataMessage { + get { + return ResourceManager.GetString("TradeFailedFakeDataMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Validation failed, the counterparty generated illegal transaction content!. + /// + internal static string TradeFailedInvalidDataMessage { + get { + return ResourceManager.GetString("TradeFailedInvalidDataMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Validation failed, invalid transaction or unsynchronized blockchain, please try again when synchronized!. + /// + internal static string TradeFailedNoSyncMessage { + get { + return ResourceManager.GetString("TradeFailedNoSyncMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Need Signature. + /// + internal static string TradeNeedSignatureCaption { + get { + return ResourceManager.GetString("TradeNeedSignatureCaption", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Transaction generated, please send the following information to the counterparty for signing:. + /// + internal static string TradeNeedSignatureMessage { + get { + return ResourceManager.GetString("TradeNeedSignatureMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Trade Request. + /// + internal static string TradeRequestCreatedCaption { + get { + return ResourceManager.GetString("TradeRequestCreatedCaption", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Transaction request generated, please send it to the counterparty or merge it with the counterparty's request.. + /// + internal static string TradeRequestCreatedMessage { + get { + return ResourceManager.GetString("TradeRequestCreatedMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Trade Success. + /// + internal static string TradeSuccessCaption { + get { + return ResourceManager.GetString("TradeSuccessCaption", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Transaction sent, this is the TXID:. + /// + internal static string TradeSuccessMessage { + get { + return ResourceManager.GetString("TradeSuccessMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to unconfirmed. + /// + internal static string Unconfirmed { + get { + return ResourceManager.GetString("Unconfirmed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to unknown issuer. + /// + internal static string UnknownIssuer { + get { + return ResourceManager.GetString("UnknownIssuer", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Blockchain unsynchronized, transaction cannot be sent.. + /// + internal static string UnsynchronizedBlock { + get { + return ResourceManager.GetString("UnsynchronizedBlock", resourceCulture); + } + } + } +} diff --git a/src/Neo.GUI/Properties/Strings.es-Es.resx b/src/Neo.GUI/Properties/Strings.es-Es.resx new file mode 100644 index 0000000000..c3ab2fa426 --- /dev/null +++ b/src/Neo.GUI/Properties/Strings.es-Es.resx @@ -0,0 +1,259 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Acerca de + + + NEO + + + Versión: + + + Fallo al añadir el contrato inteligente. Falta la correspondiente clave privada en el monedero. + + + Dirección + + + Contraseña cambiada con éxito. + + + Confirmación + + + Una vez eliminados, los activos de estas direcciones se perderán permanentemente. ¿Deseas continuar? + + + Los activos no se pueden recuperar una vez eliminados. ¿Deseas eliminarlos? + + + Confirmación + + + Notas de la transacción que se grabará en la blockchain. + + + Notas de la transacción + + + La ejecución terminó con un estado de error. + + + Caducado + + + Falló + + + Importar dirección sólo lectura + + + Transacción iniciada aunque la firma está incompleta. + + + Firma incompleta + + + Instalación del certificado cancelada. + + + Instalar certificado + + + NEO debe instalar el certificado raíz de Onchain para validar activos en la blockchain. ¿Instalar ahora? + + + Fondos insuficientes, la transacción no se puede iniciar. + + + Inválido + + + Migrar monedero + + + Abriendo ficheros de monederos antiguos, actualizar al nuevo formato? +Aviso: los ficheros actualizados no podran ser abiertos por clientes de versiones antiguas. + + + Contraseña incorrecta + + + Datos emitidos con éxito. El hash se muestra como sigue: + + + Emisión realizada con éxito + + + Raw: + + + Transacción enviada, TXID: + + + Transacción realizada con éxito + + + Falta la clave privada para firmar los datos. + + + Debes introducir el objeto JSON de los datos pendientes de firmar. + + + System + + + ¡Falló la validación! El contratante falsificó el contenido de la transacción. + + + ¡Falló la validación! El contratante generó una transacción con contenido ilegal. + + + ¡Falló la validación! Transacción no válida o blockchain sin sincronizar. Inténtalo de nuevo después de sincronizar. + + + Firma necesaria. + + + Transacción generada. Por favor, envia la siguiente información al contratante para su firma: + + + Solicitud de transacción. + + + Solicitud de transacción generada. Por favor, enviala al contratante o incorporala a la solicitud del contratante. + + + Transacción realizada con éxito. + + + Transacción enviada, este es el TXID: + + + Sin confirmar. + + + Emisor desconocido. + + + Blockchain sin sincronizar, la transacción no puede ser enviada. + + \ No newline at end of file diff --git a/src/Neo.GUI/Properties/Strings.resx b/src/Neo.GUI/Properties/Strings.resx new file mode 100644 index 0000000000..95a3fced89 --- /dev/null +++ b/src/Neo.GUI/Properties/Strings.resx @@ -0,0 +1,277 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + About + + + NEO + + + Version: + + + Failed to add smart contract, corresponding private key missing in this wallet. + + + Address + + + Cancel + + + Change password successful. + + + Confirm + + + will be consumed, confirm? + + + Cost Warning + + + Confirmation + + + Upon deletion, assets in these addresses will be permanently lost, are you sure to proceed? + + + Assets cannot be recovered once deleted, are you sure to delete the assets? + + + Confirmation + + + Enter remark here, which will be recorded on the blockchain + + + Transaction Remark + + + Execution terminated in fault state. + + + Expired + + + Failed + + + High Priority Transaction + + + Import Watch-Only Address + + + Transaction initiated, but the signature is incomplete. + + + Incomplete signature + + + You have cancelled the certificate installation. + + + Install the certificate + + + NEO must install Onchain root certificate to validate assets on the blockchain, install it now? + + + Insufficient funds, transaction cannot be initiated. + + + Invalid + + + Migrate Wallet + + + Opening wallet files in older versions, update to newest format? +Note: updated files cannot be openned by clients in older versions! + + + Wallet file relocated. New wallet file has been saved at: + + + Password Incorrect + + + Data broadcast success, the hash is shown as follows: + + + Broadcast Success + + + Raw: + + + Transaction sent, TXID: + + + Transaction successful + + + The private key that can sign the data is not found. + + + You must input JSON object pending signature data. + + + System + + + Validation failed, the counterparty falsified the transaction content! + + + Validation failed, the counterparty generated illegal transaction content! + + + Validation failed, invalid transaction or unsynchronized blockchain, please try again when synchronized! + + + Need Signature + + + Transaction generated, please send the following information to the counterparty for signing: + + + Trade Request + + + Transaction request generated, please send it to the counterparty or merge it with the counterparty's request. + + + Trade Success + + + Transaction sent, this is the TXID: + + + unconfirmed + + + unknown issuer + + + Blockchain unsynchronized, transaction cannot be sent. + + \ No newline at end of file diff --git a/src/Neo.GUI/Properties/Strings.zh-Hans.resx b/src/Neo.GUI/Properties/Strings.zh-Hans.resx new file mode 100644 index 0000000000..678c4f324d --- /dev/null +++ b/src/Neo.GUI/Properties/Strings.zh-Hans.resx @@ -0,0 +1,277 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 关于 + + + NEO + + + 版本: + + + 无法添加智能合约,因为当前钱包中不包含签署该合约的私钥。 + + + 地址 + + + 取消 + + + 修改密码成功。 + + + 确认 + + + 费用即将被消耗,确认? + + + 消费提示 + + + 删除地址确认 + + + 删除地址后,这些地址中的资产将永久性地丢失,确认要继续吗? + + + 资产删除后将无法恢复,您确定要删除以下资产吗? + + + 删除确认 + + + 请输入备注信息,该信息将被记录在区块链上 + + + 交易备注 + + + 合约执行遇到错误并退出。 + + + 证书已过期 + + + 失败 + + + 优先交易 + + + 导入监视地址 + + + 交易构造完成,但没有足够的签名: + + + 签名不完整 + + + 您已取消了证书安装过程。 + + + 安装证书 + + + NEO需要安装Onchain的根证书才能对区块链上的资产进行认证,是否现在就安装证书? + + + 余额不足,无法创建交易。 + + + 证书错误 + + + 钱包文件升级 + + + 正在打开旧版本的钱包文件,是否尝试将文件升级为新版格式? +注意,升级后将无法用旧版本的客户端打开该文件! + + + 钱包文件迁移成功,新的钱包文件已经自动保存到以下位置: + + + 密码错误! + + + 数据广播成功,这是广播数据的散列值: + + + 广播成功 + + + 原始数据: + + + 交易已发送,这是交易编号(TXID): + + + 交易成功 + + + 没有找到可以签署该数据的私钥。 + + + 必须输入一段含有待签名数据的JSON对象。 + + + NEO系统 + + + 验证失败,对方篡改了交易内容! + + + 验证失败,对方构造了非法的交易内容! + + + 验证失败,交易无效或者区块链未同步完成,请同步后再试! + + + 签名不完整 + + + 交易构造完成,请将以下信息发送给对方进行签名: + + + 交易请求 + + + 交易请求已生成,请发送给对方,或与对方的请求合并: + + + 交易成功 + + + 交易已发送,这是交易编号(TXID): + + + 未确认 + + + 未知发行者 + + + 区块链未同步完成,无法发送该交易。 + + \ No newline at end of file diff --git a/src/Neo.GUI/Resources/add.png b/src/Neo.GUI/Resources/add.png new file mode 100644 index 0000000000..08816d6519 Binary files /dev/null and b/src/Neo.GUI/Resources/add.png differ diff --git a/src/Neo.GUI/Resources/add2.png b/src/Neo.GUI/Resources/add2.png new file mode 100644 index 0000000000..9f77afc279 Binary files /dev/null and b/src/Neo.GUI/Resources/add2.png differ diff --git a/src/Neo.GUI/Resources/remark.png b/src/Neo.GUI/Resources/remark.png new file mode 100644 index 0000000000..c26fe7be39 Binary files /dev/null and b/src/Neo.GUI/Resources/remark.png differ diff --git a/src/Neo.GUI/Resources/remove.png b/src/Neo.GUI/Resources/remove.png new file mode 100644 index 0000000000..a99083bd70 Binary files /dev/null and b/src/Neo.GUI/Resources/remove.png differ diff --git a/src/Neo.GUI/Resources/search.png b/src/Neo.GUI/Resources/search.png new file mode 100644 index 0000000000..fb951a1276 Binary files /dev/null and b/src/Neo.GUI/Resources/search.png differ diff --git a/src/Neo.GUI/Resources/update.bat b/src/Neo.GUI/Resources/update.bat new file mode 100644 index 0000000000..fff10101e1 --- /dev/null +++ b/src/Neo.GUI/Resources/update.bat @@ -0,0 +1,13 @@ +@echo off +set "taskname=neo-gui.exe" +echo waiting... +:wait +ping 127.0.1 -n 3 >nul +tasklist | find "%taskname%" /i >nul 2>nul +if "%errorlevel%" NEQ "1" goto wait +echo updating... +copy /Y update\* * +rmdir /S /Q update +del /F /Q update.zip +start %taskname% +del /F /Q update.bat diff --git a/src/Neo.GUI/neo.ico b/src/Neo.GUI/neo.ico new file mode 100644 index 0000000000..141d11d686 Binary files /dev/null and b/src/Neo.GUI/neo.ico differ diff --git a/src/Neo.IO/ByteArrayComparer.cs b/src/Neo.IO/ByteArrayComparer.cs new file mode 100644 index 0000000000..f9d44e24d6 --- /dev/null +++ b/src/Neo.IO/ByteArrayComparer.cs @@ -0,0 +1,54 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ByteArrayComparer.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Neo.IO +{ + internal class ByteArrayComparer : IComparer + { + public static readonly ByteArrayComparer Default = new(1); + public static readonly ByteArrayComparer Reverse = new(-1); + + private readonly int _direction; + + private ByteArrayComparer(int direction) + { + _direction = direction; + } + + public int Compare(byte[]? x, byte[]? y) + { + if (x == y) return 0; + if (x is null && y is not null) + return _direction > 0 ? -y.Length : y.Length; + if (y is null && x is not null) + return _direction > 0 ? x.Length : -x.Length; + return _direction > 0 ? + CompareInternal(x!, y!) : + -CompareInternal(x!, y!); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int CompareInternal(byte[] x, byte[] y) + { + var length = Math.Min(x.Length, y.Length); + for (var i = 0; i < length; i++) + { + var r = x[i].CompareTo(y[i]); + if (r != 0) return r; + } + return x.Length.CompareTo(y.Length); + } + } +} diff --git a/src/Neo.IO/ByteArrayEqualityComparer.cs b/src/Neo.IO/ByteArrayEqualityComparer.cs new file mode 100644 index 0000000000..2b6f01491c --- /dev/null +++ b/src/Neo.IO/ByteArrayEqualityComparer.cs @@ -0,0 +1,58 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ByteArrayEqualityComparer.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Collections.Generic; + +namespace Neo.IO +{ + internal class ByteArrayEqualityComparer : IEqualityComparer + { + public static readonly ByteArrayEqualityComparer Default = new(); + + public unsafe bool Equals(byte[]? x, byte[]? y) + { + if (ReferenceEquals(x, y)) return true; + if (x is null || y is null) return false; + var len = x.Length; + if (len != y.Length) return false; + if (len == 0) return true; + fixed (byte* xp = x, yp = y) + { + long* xlp = (long*)xp, ylp = (long*)yp; + for (; len >= 8; len -= 8) + { + if (*xlp != *ylp) return false; + xlp++; + ylp++; + } + byte* xbp = (byte*)xlp, ybp = (byte*)ylp; + for (; len > 0; len--) + { + if (*xbp != *ybp) return false; + xbp++; + ybp++; + } + } + return true; + } + + public int GetHashCode(byte[] obj) + { + unchecked + { + var hash = 17; + foreach (var element in obj) + hash = hash * 31 + element; + return hash; + } + } + } +} diff --git a/src/Neo/IO/ISerializable.cs b/src/Neo.IO/ISerializable.cs similarity index 76% rename from src/Neo/IO/ISerializable.cs rename to src/Neo.IO/ISerializable.cs index 614854edd0..3d90009a09 100644 --- a/src/Neo/IO/ISerializable.cs +++ b/src/Neo.IO/ISerializable.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ISerializable.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/IO/MemoryReader.cs b/src/Neo.IO/MemoryReader.cs similarity index 68% rename from src/Neo/IO/MemoryReader.cs rename to src/Neo.IO/MemoryReader.cs index d29eaae01d..df8afbb3d3 100644 --- a/src/Neo/IO/MemoryReader.cs +++ b/src/Neo.IO/MemoryReader.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// MemoryReader.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -16,29 +17,29 @@ namespace Neo.IO { public ref struct MemoryReader { - private readonly ReadOnlyMemory memory; - private readonly ReadOnlySpan span; - private int pos = 0; + private readonly ReadOnlyMemory _memory; + private readonly ReadOnlySpan _span; + private int _pos = 0; - public int Position => pos; + public readonly int Position => _pos; public MemoryReader(ReadOnlyMemory memory) { - this.memory = memory; - this.span = memory.Span; + _memory = memory; + _span = memory.Span; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void EnsurePosition(int move) + private readonly void EnsurePosition(int move) { - if (pos + move > span.Length) throw new FormatException(); + if (_pos + move > _span.Length) throw new FormatException(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public byte Peek() + public readonly byte Peek() { EnsurePosition(1); - return span[pos]; + return _span[_pos]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -56,7 +57,7 @@ public bool ReadBoolean() public sbyte ReadSByte() { EnsurePosition(1); - byte b = span[pos++]; + var b = _span[_pos++]; return unchecked((sbyte)b); } @@ -64,15 +65,15 @@ public sbyte ReadSByte() public byte ReadByte() { EnsurePosition(1); - return span[pos++]; + return _span[_pos++]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public short ReadInt16() { EnsurePosition(sizeof(short)); - var result = BinaryPrimitives.ReadInt16LittleEndian(span[pos..]); - pos += sizeof(short); + var result = BinaryPrimitives.ReadInt16LittleEndian(_span[_pos..]); + _pos += sizeof(short); return result; } @@ -80,8 +81,8 @@ public short ReadInt16() public short ReadInt16BigEndian() { EnsurePosition(sizeof(short)); - var result = BinaryPrimitives.ReadInt16BigEndian(span[pos..]); - pos += sizeof(short); + var result = BinaryPrimitives.ReadInt16BigEndian(_span[_pos..]); + _pos += sizeof(short); return result; } @@ -89,8 +90,8 @@ public short ReadInt16BigEndian() public ushort ReadUInt16() { EnsurePosition(sizeof(ushort)); - var result = BinaryPrimitives.ReadUInt16LittleEndian(span[pos..]); - pos += sizeof(ushort); + var result = BinaryPrimitives.ReadUInt16LittleEndian(_span[_pos..]); + _pos += sizeof(ushort); return result; } @@ -98,8 +99,8 @@ public ushort ReadUInt16() public ushort ReadUInt16BigEndian() { EnsurePosition(sizeof(ushort)); - var result = BinaryPrimitives.ReadUInt16BigEndian(span[pos..]); - pos += sizeof(ushort); + var result = BinaryPrimitives.ReadUInt16BigEndian(_span[_pos..]); + _pos += sizeof(ushort); return result; } @@ -107,8 +108,8 @@ public ushort ReadUInt16BigEndian() public int ReadInt32() { EnsurePosition(sizeof(int)); - var result = BinaryPrimitives.ReadInt32LittleEndian(span[pos..]); - pos += sizeof(int); + var result = BinaryPrimitives.ReadInt32LittleEndian(_span[_pos..]); + _pos += sizeof(int); return result; } @@ -116,8 +117,8 @@ public int ReadInt32() public int ReadInt32BigEndian() { EnsurePosition(sizeof(int)); - var result = BinaryPrimitives.ReadInt32BigEndian(span[pos..]); - pos += sizeof(int); + var result = BinaryPrimitives.ReadInt32BigEndian(_span[_pos..]); + _pos += sizeof(int); return result; } @@ -125,8 +126,8 @@ public int ReadInt32BigEndian() public uint ReadUInt32() { EnsurePosition(sizeof(uint)); - var result = BinaryPrimitives.ReadUInt32LittleEndian(span[pos..]); - pos += sizeof(uint); + var result = BinaryPrimitives.ReadUInt32LittleEndian(_span[_pos..]); + _pos += sizeof(uint); return result; } @@ -134,8 +135,8 @@ public uint ReadUInt32() public uint ReadUInt32BigEndian() { EnsurePosition(sizeof(uint)); - var result = BinaryPrimitives.ReadUInt32BigEndian(span[pos..]); - pos += sizeof(uint); + var result = BinaryPrimitives.ReadUInt32BigEndian(_span[_pos..]); + _pos += sizeof(uint); return result; } @@ -143,8 +144,8 @@ public uint ReadUInt32BigEndian() public long ReadInt64() { EnsurePosition(sizeof(long)); - var result = BinaryPrimitives.ReadInt64LittleEndian(span[pos..]); - pos += sizeof(long); + var result = BinaryPrimitives.ReadInt64LittleEndian(_span[_pos..]); + _pos += sizeof(long); return result; } @@ -152,8 +153,8 @@ public long ReadInt64() public long ReadInt64BigEndian() { EnsurePosition(sizeof(long)); - var result = BinaryPrimitives.ReadInt64BigEndian(span[pos..]); - pos += sizeof(long); + var result = BinaryPrimitives.ReadInt64BigEndian(_span[_pos..]); + _pos += sizeof(long); return result; } @@ -161,8 +162,8 @@ public long ReadInt64BigEndian() public ulong ReadUInt64() { EnsurePosition(sizeof(ulong)); - var result = BinaryPrimitives.ReadUInt64LittleEndian(span[pos..]); - pos += sizeof(ulong); + var result = BinaryPrimitives.ReadUInt64LittleEndian(_span[_pos..]); + _pos += sizeof(ulong); return result; } @@ -170,16 +171,16 @@ public ulong ReadUInt64() public ulong ReadUInt64BigEndian() { EnsurePosition(sizeof(ulong)); - var result = BinaryPrimitives.ReadUInt64BigEndian(span[pos..]); - pos += sizeof(ulong); + var result = BinaryPrimitives.ReadUInt64BigEndian(_span[_pos..]); + _pos += sizeof(ulong); return result; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ulong ReadVarInt(ulong max = ulong.MaxValue) { - byte b = ReadByte(); - ulong value = b switch + var b = ReadByte(); + var value = b switch { 0xfd => ReadUInt16(), 0xfe => ReadUInt32(), @@ -194,24 +195,24 @@ public ulong ReadVarInt(ulong max = ulong.MaxValue) public string ReadFixedString(int length) { EnsurePosition(length); - int end = pos + length; - int i = pos; - while (i < end && span[i] != 0) i++; - ReadOnlySpan data = span[pos..i]; + var end = _pos + length; + var i = _pos; + while (i < end && _span[i] != 0) i++; + var data = _span[_pos..i]; for (; i < end; i++) - if (span[i] != 0) + if (_span[i] != 0) throw new FormatException(); - pos = end; + _pos = end; return Utility.StrictUTF8.GetString(data); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public string ReadVarString(int max = 0x1000000) { - int length = (int)ReadVarInt((ulong)max); + var length = (int)ReadVarInt((ulong)max); EnsurePosition(length); - ReadOnlySpan data = span.Slice(pos, length); - pos += length; + var data = _span.Slice(_pos, length); + _pos += length; return Utility.StrictUTF8.GetString(data); } @@ -219,22 +220,20 @@ public string ReadVarString(int max = 0x1000000) public ReadOnlyMemory ReadMemory(int count) { EnsurePosition(count); - ReadOnlyMemory result = memory.Slice(pos, count); - pos += count; + var result = _memory.Slice(_pos, count); + _pos += count; return result; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlyMemory ReadVarMemory(int max = 0x1000000) - { - return ReadMemory((int)ReadVarInt((ulong)max)); - } + public ReadOnlyMemory ReadVarMemory(int max = 0x1000000) => + ReadMemory((int)ReadVarInt((ulong)max)); [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlyMemory ReadToEnd() { - ReadOnlyMemory result = memory[pos..]; - pos = memory.Length; + var result = _memory[_pos..]; + _pos = _memory.Length; return result; } } diff --git a/src/Neo.IO/Neo.IO.csproj b/src/Neo.IO/Neo.IO.csproj new file mode 100644 index 0000000000..a51ec1110a --- /dev/null +++ b/src/Neo.IO/Neo.IO.csproj @@ -0,0 +1,22 @@ + + + + netstandard2.1;net8.0 + true + enable + Neo.IO + NEO;Blockchain;IO + ../../bin/$(PackageId) + + + + + + + + + + + + + diff --git a/src/Neo.Json/JArray.cs b/src/Neo.Json/JArray.cs index 0263c77a3a..903f29941b 100644 --- a/src/Neo.Json/JArray.cs +++ b/src/Neo.Json/JArray.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The Neo.Json is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// JArray.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -122,7 +123,7 @@ internal override void Write(Utf8JsonWriter writer) writer.WriteEndArray(); } - public override JArray Clone() + public override JToken Clone() { var cloned = new JArray(); diff --git a/src/Neo.Json/JBoolean.cs b/src/Neo.Json/JBoolean.cs index 9398270aae..3c2b025a54 100644 --- a/src/Neo.Json/JBoolean.cs +++ b/src/Neo.Json/JBoolean.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The Neo.Json is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// JBoolean.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -28,7 +29,7 @@ public class JBoolean : JToken /// The value of the JSON token. public JBoolean(bool value = false) { - this.Value = value; + Value = value; } public override bool AsBoolean() @@ -62,7 +63,7 @@ internal override void Write(Utf8JsonWriter writer) writer.WriteBooleanValue(Value); } - public override JBoolean Clone() + public override JToken Clone() { return this; } @@ -71,5 +72,33 @@ public static implicit operator JBoolean(bool value) { return new JBoolean(value); } + + public static bool operator ==(JBoolean left, JBoolean right) + { + return left.Value.Equals(right.Value); + } + + public static bool operator !=(JBoolean left, JBoolean right) + { + return !left.Value.Equals(right.Value); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(this, obj)) + { + return true; + } + if (obj is JBoolean other) + { + return Value.Equals(other.Value); + } + return false; + } + + public override int GetHashCode() + { + return Value.GetHashCode(); + } } } diff --git a/src/Neo.Json/JContainer.cs b/src/Neo.Json/JContainer.cs index 429fe68d81..5cc1bc7263 100644 --- a/src/Neo.Json/JContainer.cs +++ b/src/Neo.Json/JContainer.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The Neo.Json is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// JContainer.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo.Json/JNumber.cs b/src/Neo.Json/JNumber.cs index 7e58b72567..1ae5d6d0a2 100644 --- a/src/Neo.Json/JNumber.cs +++ b/src/Neo.Json/JNumber.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The Neo.Json is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// JNumber.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -40,7 +41,7 @@ public class JNumber : JToken public JNumber(double value = 0) { if (!double.IsFinite(value)) throw new FormatException(); - this.Value = value; + Value = value; } /// @@ -108,7 +109,7 @@ internal override void Write(Utf8JsonWriter writer) writer.WriteNumberValue(Value); } - public override JNumber Clone() + public override JToken Clone() { return this; } @@ -117,5 +118,50 @@ public static implicit operator JNumber(double value) { return new JNumber(value); } + + public static implicit operator JNumber(long value) + { + return new JNumber(value); + } + + public static bool operator ==(JNumber left, JNumber? right) + { + if (right is null) return false; + return ReferenceEquals(left, right) || left.Value.Equals(right.Value); + } + + public static bool operator !=(JNumber left, JNumber right) + { + return !(left == right); + } + + public override bool Equals(object? obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + + var other = obj switch + { + JNumber jNumber => jNumber, + uint u => new JNumber(u), + int i => new JNumber(i), + ulong ul => new JNumber(ul), + long l => new JNumber(l), + byte b => new JNumber(b), + sbyte sb => new JNumber(sb), + short s => new JNumber(s), + ushort us => new JNumber(us), + decimal d => new JNumber((double)d), + float f => new JNumber(f), + double d => new JNumber(d), + _ => throw new ArgumentOutOfRangeException(nameof(obj), obj, null) + }; + return other == this; + } + + public override int GetHashCode() + { + return Value.GetHashCode(); + } } } diff --git a/src/Neo.Json/JObject.cs b/src/Neo.Json/JObject.cs index dc26463210..c5666e9112 100644 --- a/src/Neo.Json/JObject.cs +++ b/src/Neo.Json/JObject.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The Neo.Json is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// JObject.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -75,7 +76,7 @@ internal override void Write(Utf8JsonWriter writer) /// Creates a copy of the current JSON object. /// /// A copy of the current JSON object. - public override JObject Clone() + public override JToken Clone() { var cloned = new JObject(); diff --git a/src/Neo.Json/JPathToken.cs b/src/Neo.Json/JPathToken.cs index b2d8a867fe..40054a1fb0 100644 --- a/src/Neo.Json/JPathToken.cs +++ b/src/Neo.Json/JPathToken.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The Neo.Json is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// JPathToken.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo.Json/JPathTokenType.cs b/src/Neo.Json/JPathTokenType.cs index 7ec540372e..ea25a3609d 100644 --- a/src/Neo.Json/JPathTokenType.cs +++ b/src/Neo.Json/JPathTokenType.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The Neo.Json is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// JPathTokenType.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo.Json/JString.cs b/src/Neo.Json/JString.cs index 714ff85928..215ebdf730 100644 --- a/src/Neo.Json/JString.cs +++ b/src/Neo.Json/JString.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The Neo.Json is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// JString.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -29,7 +30,7 @@ public class JString : JToken /// The value of the JSON token. public JString(string value) { - this.Value = value ?? throw new ArgumentNullException(nameof(value)); + Value = value ?? throw new ArgumentNullException(nameof(value)); } /// @@ -69,7 +70,7 @@ public override T AsEnum(T defaultValue = default, bool ignoreCase = false) public override T GetEnum(bool ignoreCase = false) { T result = Enum.Parse(Value, ignoreCase); - if (!Enum.IsDefined(result)) throw new InvalidCastException(); + if (!Enum.IsDefined(typeof(T), result)) throw new InvalidCastException(); return result; } @@ -78,7 +79,7 @@ internal override void Write(Utf8JsonWriter writer) writer.WriteStringValue(Value); } - public override JString Clone() + public override JToken Clone() { return this; } @@ -92,5 +93,36 @@ public static implicit operator JString(Enum value) { return value == null ? null : new JString(value); } + + public static bool operator ==(JString left, JString? right) + { + if (right is null) return false; + return ReferenceEquals(left, right) || left.Value.Equals(right.Value); + } + + public static bool operator !=(JString left, JString right) + { + return !(left == right); + } + + public override bool Equals(object? obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj is JString other) + { + return this == other; + } + if (obj is string str) + { + return Value == str; + } + return false; + } + + public override int GetHashCode() + { + return Value.GetHashCode(); + } } } diff --git a/src/Neo.Json/JToken.cs b/src/Neo.Json/JToken.cs index 63ab9f4752..bb6a510a69 100644 --- a/src/Neo.Json/JToken.cs +++ b/src/Neo.Json/JToken.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The Neo.Json is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// JToken.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -127,7 +128,7 @@ public int GetInt32() /// The byte array that contains the JSON token. /// The maximum nesting depth when parsing the JSON token. /// The parsed JSON token. - public static JToken? Parse(ReadOnlySpan value, int max_nest = 100) + public static JToken? Parse(ReadOnlySpan value, int max_nest = 64) { Utf8JsonReader reader = new(value, new JsonReaderOptions { @@ -153,7 +154,7 @@ public int GetInt32() /// The that contains the JSON token. /// The maximum nesting depth when parsing the JSON token. /// The parsed JSON token. - public static JToken? Parse(string value, int max_nest = 100) + public static JToken? Parse(string value, int max_nest = 64) { return Parse(StrictUTF8.GetBytes(value), max_nest); } diff --git a/src/Neo.Json/Neo.Json.csproj b/src/Neo.Json/Neo.Json.csproj index 94943d4402..15924436e7 100644 --- a/src/Neo.Json/Neo.Json.csproj +++ b/src/Neo.Json/Neo.Json.csproj @@ -1,9 +1,16 @@ - - - - enable - enable - NEO;JSON - - - + + + + netstandard2.1;net8.0 + enable + enable + Neo.Json + NEO;JSON + ../../bin/$(PackageId) + + + + + + + diff --git a/src/Neo.Json/OrderedDictionary.KeyCollection.cs b/src/Neo.Json/OrderedDictionary.KeyCollection.cs index bdc79411f3..13fbd62e85 100644 --- a/src/Neo.Json/OrderedDictionary.KeyCollection.cs +++ b/src/Neo.Json/OrderedDictionary.KeyCollection.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The Neo.Json is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// OrderedDictionary.KeyCollection.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo.Json/OrderedDictionary.ValueCollection.cs b/src/Neo.Json/OrderedDictionary.ValueCollection.cs index f10425aca8..f7bb0c4359 100644 --- a/src/Neo.Json/OrderedDictionary.ValueCollection.cs +++ b/src/Neo.Json/OrderedDictionary.ValueCollection.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The Neo.Json is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// OrderedDictionary.ValueCollection.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo.Json/OrderedDictionary.cs b/src/Neo.Json/OrderedDictionary.cs index 261b15a725..0913bda44e 100644 --- a/src/Neo.Json/OrderedDictionary.cs +++ b/src/Neo.Json/OrderedDictionary.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The Neo.Json is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// OrderedDictionary.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -89,6 +90,8 @@ public bool Remove(TKey key) return collection.Remove(key); } +#pragma warning disable CS8767 + public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) { if (collection.TryGetValue(key, out var entry)) @@ -100,6 +103,8 @@ public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) return false; } +#pragma warning restore CS8767 + void ICollection>.Add(KeyValuePair item) { Add(item.Key, item.Value); diff --git a/src/Neo.Json/Properties/AssemblyInfo.cs b/src/Neo.Json/Properties/AssemblyInfo.cs index 1d0d02140b..9c3afc5d2e 100644 --- a/src/Neo.Json/Properties/AssemblyInfo.cs +++ b/src/Neo.Json/Properties/AssemblyInfo.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// AssemblyInfo.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Neo.Json.UnitTests")] diff --git a/src/Neo.Json/Utility.cs b/src/Neo.Json/Utility.cs index 711ba25901..6fff7211ca 100644 --- a/src/Neo.Json/Utility.cs +++ b/src/Neo.Json/Utility.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The Neo.Json is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// Utility.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo.VM/BadScriptException.cs b/src/Neo.VM/BadScriptException.cs new file mode 100644 index 0000000000..1e7b250291 --- /dev/null +++ b/src/Neo.VM/BadScriptException.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// BadScriptException.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; + +namespace Neo.VM +{ + /// + /// Represents the exception thrown when the bad script is parsed. + /// + public class BadScriptException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public BadScriptException() { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The message that describes the error. + public BadScriptException(string message) : base(message) { } + } +} diff --git a/src/Neo.VM/CatchableException.cs b/src/Neo.VM/CatchableException.cs new file mode 100644 index 0000000000..b4fd3f1655 --- /dev/null +++ b/src/Neo.VM/CatchableException.cs @@ -0,0 +1,22 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// CatchableException.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; + +namespace Neo.VM +{ + public class CatchableException : Exception + { + public CatchableException(string message) : base(message) + { + } + } +} diff --git a/src/Neo.VM/Collections/OrderedDictionary.cs b/src/Neo.VM/Collections/OrderedDictionary.cs new file mode 100644 index 0000000000..82eba05d7a --- /dev/null +++ b/src/Neo.VM/Collections/OrderedDictionary.cs @@ -0,0 +1,130 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// OrderedDictionary.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace Neo.VM.Collections +{ + internal class OrderedDictionary : IDictionary + where TKey : notnull + { + private class TItem + { + public readonly TKey Key; + public TValue Value; + + public TItem(TKey key, TValue value) + { + Key = key; + Value = value; + } + } + + private class InternalCollection : KeyedCollection + { + protected override TKey GetKeyForItem(TItem item) + { + return item.Key; + } + } + + private readonly InternalCollection collection = new(); + + public int Count => collection.Count; + public bool IsReadOnly => false; + public ICollection Keys => collection.Select(p => p.Key).ToArray(); + public ICollection Values => collection.Select(p => p.Value).ToArray(); + + public TValue this[TKey key] + { + get + { + return collection[key].Value; + } + set + { + if (collection.TryGetValue(key, out var entry)) + entry.Value = value; + else + Add(key, value); + } + } + + public void Add(TKey key, TValue value) + { + collection.Add(new TItem(key, value)); + } + + public bool ContainsKey(TKey key) + { + return collection.Contains(key); + } + + public bool Remove(TKey key) + { + return collection.Remove(key); + } + + // supress warning of value parameter nullability mismatch +#pragma warning disable CS8767 + public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) +#pragma warning restore CS8767 + { + if (collection.TryGetValue(key, out var entry)) + { + value = entry.Value; + return true; + } + value = default; + return false; + } + + void ICollection>.Add(KeyValuePair item) + { + Add(item.Key, item.Value); + } + + public void Clear() + { + collection.Clear(); + } + + bool ICollection>.Contains(KeyValuePair item) + { + return collection.Contains(item.Key); + } + + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + { + for (int i = 0; i < collection.Count; i++) + array[i + arrayIndex] = new KeyValuePair(collection[i].Key, collection[i].Value); + } + + bool ICollection>.Remove(KeyValuePair item) + { + return collection.Remove(item.Key); + } + + IEnumerator> IEnumerable>.GetEnumerator() + { + return collection.Select(p => new KeyValuePair(p.Key, p.Value)).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return collection.Select(p => new KeyValuePair(p.Key, p.Value)).GetEnumerator(); + } + } +} diff --git a/src/Neo.VM/Cryptography/BitOperations.cs b/src/Neo.VM/Cryptography/BitOperations.cs new file mode 100644 index 0000000000..c947b4de98 --- /dev/null +++ b/src/Neo.VM/Cryptography/BitOperations.cs @@ -0,0 +1,28 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// BitOperations.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Runtime.CompilerServices; + +namespace Neo.VM.Cryptography +{ +#if !NET5_0_OR_GREATER + static class BitOperations + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint RotateLeft(uint value, int offset) + => (value << offset) | (value >> (32 - offset)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong RotateLeft(ulong value, int offset) + => (value << offset) | (value >> (64 - offset)); + } +#endif +} diff --git a/src/Neo.VM/Cryptography/Murmur32.cs b/src/Neo.VM/Cryptography/Murmur32.cs new file mode 100644 index 0000000000..cfb6f8ccd1 --- /dev/null +++ b/src/Neo.VM/Cryptography/Murmur32.cs @@ -0,0 +1,98 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Murmur32.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Buffers.Binary; +using System.Numerics; +using System.Security.Cryptography; + +namespace Neo.VM.Cryptography +{ + /// + /// Computes the murmur hash for the input data. + /// + sealed class Murmur32 : HashAlgorithm + { + private const uint c1 = 0xcc9e2d51; + private const uint c2 = 0x1b873593; + private const int r1 = 15; + private const int r2 = 13; + private const uint m = 5; + private const uint n = 0xe6546b64; + + private readonly uint seed; + private uint hash; + private int length; + + public override int HashSize => 32; + + /// + /// Initializes a new instance of the class with the specified seed. + /// + /// The seed to be used. + public Murmur32(uint seed) + { + this.seed = seed; + Initialize(); + } + + protected override void HashCore(byte[] array, int ibStart, int cbSize) + { + length += cbSize; + int remainder = cbSize & 3; + int alignedLength = ibStart + (cbSize - remainder); + for (int i = ibStart; i < alignedLength; i += 4) + { + uint k = BinaryPrimitives.ReadUInt32LittleEndian(array.AsSpan(i)); + k *= c1; + k = BitOperations.RotateLeft(k, r1); + k *= c2; + hash ^= k; + hash = BitOperations.RotateLeft(hash, r2); + hash = hash * m + n; + } + if (remainder > 0) + { + uint remainingBytes = 0; + switch (remainder) + { + case 3: remainingBytes ^= (uint)array[alignedLength + 2] << 16; goto case 2; + case 2: remainingBytes ^= (uint)array[alignedLength + 1] << 8; goto case 1; + case 1: remainingBytes ^= array[alignedLength]; break; + } + remainingBytes *= c1; + remainingBytes = BitOperations.RotateLeft(remainingBytes, r1); + remainingBytes *= c2; + hash ^= remainingBytes; + } + } + + protected override byte[] HashFinal() + { + hash ^= (uint)length; + hash ^= hash >> 16; + hash *= 0x85ebca6b; + hash ^= hash >> 13; + hash *= 0xc2b2ae35; + hash ^= hash >> 16; + + byte[] buffer = new byte[sizeof(uint)]; + BinaryPrimitives.WriteUInt32LittleEndian(buffer, hash); + return buffer; + } + + public override void Initialize() + { + hash = seed; + length = 0; + } + } +} diff --git a/src/Neo.VM/Debugger.cs b/src/Neo.VM/Debugger.cs new file mode 100644 index 0000000000..a19ae7d64e --- /dev/null +++ b/src/Neo.VM/Debugger.cs @@ -0,0 +1,138 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Debugger.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Collections.Generic; + +namespace Neo.VM +{ + /// + /// A simple debugger for . + /// + public class Debugger + { + private readonly ExecutionEngine engine; + private readonly Dictionary> break_points = new(); + + /// + /// Create a debugger on the specified . + /// + /// The to attach the debugger. + public Debugger(ExecutionEngine engine) + { + this.engine = engine; + } + + /// + /// Add a breakpoint at the specified position of the specified script. The VM will break the execution when it reaches the breakpoint. + /// + /// The script to add the breakpoint. + /// The position of the breakpoint in the script. + public void AddBreakPoint(Script script, uint position) + { + if (!break_points.TryGetValue(script, out HashSet? hashset)) + { + hashset = new HashSet(); + break_points.Add(script, hashset); + } + hashset.Add(position); + } + + /// + /// Start or continue execution of the VM. + /// + /// Returns the state of the VM after the execution. + public VMState Execute() + { + if (engine.State == VMState.BREAK) + engine.State = VMState.NONE; + while (engine.State == VMState.NONE) + ExecuteAndCheckBreakPoints(); + return engine.State; + } + + private void ExecuteAndCheckBreakPoints() + { + engine.ExecuteNext(); + if (engine.State == VMState.NONE && engine.InvocationStack.Count > 0 && break_points.Count > 0) + { + if (break_points.TryGetValue(engine.CurrentContext!.Script, out HashSet? hashset) && hashset.Contains((uint)engine.CurrentContext.InstructionPointer)) + engine.State = VMState.BREAK; + } + } + + /// + /// Removes the breakpoint at the specified position in the specified script. + /// + /// The script to remove the breakpoint. + /// The position of the breakpoint in the script. + /// + /// if the breakpoint is successfully found and removed; + /// otherwise, . + /// + public bool RemoveBreakPoint(Script script, uint position) + { + if (!break_points.TryGetValue(script, out HashSet? hashset)) return false; + if (!hashset.Remove(position)) return false; + if (hashset.Count == 0) break_points.Remove(script); + return true; + } + + /// + /// Execute the next instruction. If the instruction involves a call to a method, it steps into the method and breaks the execution on the first instruction of that method. + /// + /// The VM state after the instruction is executed. + public VMState StepInto() + { + if (engine.State == VMState.HALT || engine.State == VMState.FAULT) + return engine.State; + engine.ExecuteNext(); + if (engine.State == VMState.NONE) + engine.State = VMState.BREAK; + return engine.State; + } + + /// + /// Execute until the currently executed method is returned. + /// + /// The VM state after the currently executed method is returned. + public VMState StepOut() + { + if (engine.State == VMState.BREAK) + engine.State = VMState.NONE; + int c = engine.InvocationStack.Count; + while (engine.State == VMState.NONE && engine.InvocationStack.Count >= c) + ExecuteAndCheckBreakPoints(); + if (engine.State == VMState.NONE) + engine.State = VMState.BREAK; + return engine.State; + } + + /// + /// Execute the next instruction. If the instruction involves a call to a method, it does not step into the method (it steps over it instead). + /// + /// The VM state after the instruction is executed. + public VMState StepOver() + { + if (engine.State == VMState.HALT || engine.State == VMState.FAULT) + return engine.State; + engine.State = VMState.NONE; + int c = engine.InvocationStack.Count; + do + { + ExecuteAndCheckBreakPoints(); + } + while (engine.State == VMState.NONE && engine.InvocationStack.Count > c); + if (engine.State == VMState.NONE) + engine.State = VMState.BREAK; + return engine.State; + } + } +} diff --git a/src/Neo.VM/EvaluationStack.cs b/src/Neo.VM/EvaluationStack.cs new file mode 100644 index 0000000000..34517b2197 --- /dev/null +++ b/src/Neo.VM/EvaluationStack.cs @@ -0,0 +1,168 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// EvaluationStack.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.VM.Types; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Neo.VM +{ + /// + /// Represents the evaluation stack in the VM. + /// + public sealed class EvaluationStack : IReadOnlyList + { + private readonly List innerList = new(); + private readonly ReferenceCounter referenceCounter; + + internal EvaluationStack(ReferenceCounter referenceCounter) + { + this.referenceCounter = referenceCounter; + } + + /// + /// Gets the number of items on the stack. + /// + public int Count => innerList.Count; + + internal void Clear() + { + foreach (StackItem item in innerList) + referenceCounter.RemoveStackReference(item); + innerList.Clear(); + } + + internal void CopyTo(EvaluationStack stack, int count = -1) + { + if (count < -1 || count > innerList.Count) + throw new ArgumentOutOfRangeException(nameof(count)); + if (count == 0) return; + if (count == -1 || count == innerList.Count) + stack.innerList.AddRange(innerList); + else + stack.innerList.AddRange(innerList.Skip(innerList.Count - count)); + } + + public IEnumerator GetEnumerator() + { + return innerList.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return innerList.GetEnumerator(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void Insert(int index, StackItem item) + { + if (index > innerList.Count) throw new InvalidOperationException($"Insert out of bounds: {index}/{innerList.Count}"); + innerList.Insert(innerList.Count - index, item); + referenceCounter.AddStackReference(item); + } + + internal void MoveTo(EvaluationStack stack, int count = -1) + { + if (count == 0) return; + CopyTo(stack, count); + if (count == -1 || count == innerList.Count) + innerList.Clear(); + else + innerList.RemoveRange(innerList.Count - count, count); + } + + /// + /// Returns the item at the specified index from the top of the stack without removing it. + /// + /// The index of the object from the top of the stack. + /// The item at the specified index. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public StackItem Peek(int index = 0) + { + if (index >= innerList.Count) throw new InvalidOperationException($"Peek out of bounds: {index}/{innerList.Count}"); + if (index < 0) + { + index += innerList.Count; + if (index < 0) throw new InvalidOperationException($"Peek out of bounds: {index}/{innerList.Count}"); + } + return innerList[innerList.Count - index - 1]; + } + + StackItem IReadOnlyList.this[int index] => Peek(index); + + /// + /// Pushes an item onto the top of the stack. + /// + /// The item to be pushed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Push(StackItem item) + { + innerList.Add(item); + referenceCounter.AddStackReference(item); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void Reverse(int n) + { + if (n < 0 || n > innerList.Count) + throw new ArgumentOutOfRangeException(nameof(n)); + if (n <= 1) return; + innerList.Reverse(innerList.Count - n, n); + } + + /// + /// Removes and returns the item at the top of the stack. + /// + /// The item removed from the top of the stack. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public StackItem Pop() + { + return Remove(0); + } + + /// + /// Removes and returns the item at the top of the stack and convert it to the specified type. + /// + /// The type to convert to. + /// The item removed from the top of the stack. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Pop() where T : StackItem + { + return Remove(0); + } + + internal T Remove(int index) where T : StackItem + { + if (index >= innerList.Count) + throw new ArgumentOutOfRangeException(nameof(index)); + if (index < 0) + { + index += innerList.Count; + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(index)); + } + index = innerList.Count - index - 1; + if (innerList[index] is not T item) + throw new InvalidCastException($"The item can't be casted to type {typeof(T)}"); + innerList.RemoveAt(index); + referenceCounter.RemoveStackReference(item); + return item; + } + + public override string ToString() + { + return $"[{string.Join(", ", innerList.Select(p => $"{p.Type}({p})"))}]"; + } + } +} diff --git a/src/Neo.VM/ExceptionHandlingContext.cs b/src/Neo.VM/ExceptionHandlingContext.cs new file mode 100644 index 0000000000..38c321ed17 --- /dev/null +++ b/src/Neo.VM/ExceptionHandlingContext.cs @@ -0,0 +1,58 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ExceptionHandlingContext.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Diagnostics; + +namespace Neo.VM +{ + /// + /// Represents the context used for exception handling. + /// + [DebuggerDisplay("State={State}, CatchPointer={CatchPointer}, FinallyPointer={FinallyPointer}, EndPointer={EndPointer}")] + public sealed class ExceptionHandlingContext + { + /// + /// The position of the block. + /// + public int CatchPointer { get; } + + /// + /// The position of the block. + /// + public int FinallyPointer { get; } + + /// + /// The end position of the -- block. + /// + public int EndPointer { get; internal set; } = -1; + + /// + /// Indicates whether the block is included in the context. + /// + public bool HasCatch => CatchPointer >= 0; + + /// + /// Indicates whether the block is included in the context. + /// + public bool HasFinally => FinallyPointer >= 0; + + /// + /// Indicates the state of the context. + /// + public ExceptionHandlingState State { get; internal set; } = ExceptionHandlingState.Try; + + internal ExceptionHandlingContext(int catchPointer, int finallyPointer) + { + CatchPointer = catchPointer; + FinallyPointer = finallyPointer; + } + } +} diff --git a/src/Neo.VM/ExceptionHandlingState.cs b/src/Neo.VM/ExceptionHandlingState.cs new file mode 100644 index 0000000000..119307a29d --- /dev/null +++ b/src/Neo.VM/ExceptionHandlingState.cs @@ -0,0 +1,34 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ExceptionHandlingState.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.VM +{ + /// + /// Indicates the state of the . + /// + public enum ExceptionHandlingState : byte + { + /// + /// Indicates that the block is being executed. + /// + Try, + + /// + /// Indicates that the block is being executed. + /// + Catch, + + /// + /// Indicates that the block is being executed. + /// + Finally + } +} diff --git a/src/Neo.VM/ExecutionContext.SharedStates.cs b/src/Neo.VM/ExecutionContext.SharedStates.cs new file mode 100644 index 0000000000..afa01d7995 --- /dev/null +++ b/src/Neo.VM/ExecutionContext.SharedStates.cs @@ -0,0 +1,34 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ExecutionContext.SharedStates.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Collections.Generic; + +namespace Neo.VM +{ + partial class ExecutionContext + { + private class SharedStates + { + public readonly Script Script; + public readonly EvaluationStack EvaluationStack; + public Slot? StaticFields; + public readonly Dictionary States; + + public SharedStates(Script script, ReferenceCounter referenceCounter) + { + Script = script; + EvaluationStack = new EvaluationStack(referenceCounter); + States = new Dictionary(); + } + } + } +} diff --git a/src/Neo.VM/ExecutionContext.cs b/src/Neo.VM/ExecutionContext.cs new file mode 100644 index 0000000000..d1f9a54b1d --- /dev/null +++ b/src/Neo.VM/ExecutionContext.cs @@ -0,0 +1,170 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ExecutionContext.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Neo.VM +{ + /// + /// Represents a frame in the VM execution stack. + /// + [DebuggerDisplay("InstructionPointer={InstructionPointer}")] + public sealed partial class ExecutionContext + { + private readonly SharedStates shared_states; + private int instructionPointer; + + /// + /// Indicates the number of values that the context should return when it is unloaded. + /// + public int RVCount { get; } + + /// + /// The script to run in this context. + /// + public Script Script => shared_states.Script; + + /// + /// The evaluation stack for this context. + /// + public EvaluationStack EvaluationStack => shared_states.EvaluationStack; + + /// + /// The slot used to store the static fields. + /// + public Slot? StaticFields + { + get => shared_states.StaticFields; + internal set => shared_states.StaticFields = value; + } + + /// + /// The slot used to store the local variables of the current method. + /// + public Slot? LocalVariables { get; internal set; } + + /// + /// The slot used to store the arguments of the current method. + /// + public Slot? Arguments { get; internal set; } + + /// + /// The stack containing nested . + /// + public Stack? TryStack { get; internal set; } + + /// + /// The pointer indicating the current instruction. + /// + public int InstructionPointer + { + get + { + return instructionPointer; + } + internal set + { + if (value < 0 || value > Script.Length) + throw new ArgumentOutOfRangeException(nameof(value)); + instructionPointer = value; + } + } + + /// + /// Returns the current . + /// + public Instruction? CurrentInstruction + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return GetInstruction(InstructionPointer); + } + } + + /// + /// Returns the next . + /// + public Instruction? NextInstruction + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + Instruction? current = CurrentInstruction; + if (current is null) return null; + return GetInstruction(InstructionPointer + current.Size); + } + } + + internal ExecutionContext(Script script, int rvcount, ReferenceCounter referenceCounter) + : this(new SharedStates(script, referenceCounter), rvcount, 0) + { + } + + private ExecutionContext(SharedStates shared_states, int rvcount, int initialPosition) + { + if (rvcount < -1 || rvcount > ushort.MaxValue) + throw new ArgumentOutOfRangeException(nameof(rvcount)); + this.shared_states = shared_states; + RVCount = rvcount; + InstructionPointer = initialPosition; + } + + /// + /// Clones the context so that they share the same script, stack, and static fields. + /// + /// The cloned context. + public ExecutionContext Clone() + { + return Clone(InstructionPointer); + } + + /// + /// Clones the context so that they share the same script, stack, and static fields. + /// + /// The instruction pointer of the new context. + /// The cloned context. + public ExecutionContext Clone(int initialPosition) + { + return new ExecutionContext(shared_states, 0, initialPosition); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private Instruction? GetInstruction(int ip) => ip >= Script.Length ? null : Script.GetInstruction(ip); + + /// + /// Gets custom data of the specified type. If the data does not exist, create a new one. + /// + /// The type of data to be obtained. + /// A delegate used to create the entry. If factory is null, new() will be used. + /// The custom data of the specified type. + public T GetState(Func? factory = null) where T : class, new() + { + if (!shared_states.States.TryGetValue(typeof(T), out object? value)) + { + value = factory is null ? new T() : factory(); + shared_states.States[typeof(T)] = value; + } + return (T)value; + } + + internal bool MoveNext() + { + Instruction? current = CurrentInstruction; + if (current is null) return false; + InstructionPointer += current.Size; + return InstructionPointer < Script.Length; + } + } +} diff --git a/src/Neo.VM/ExecutionEngine.cs b/src/Neo.VM/ExecutionEngine.cs new file mode 100644 index 0000000000..c7c3f86ce9 --- /dev/null +++ b/src/Neo.VM/ExecutionEngine.cs @@ -0,0 +1,304 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ExecutionEngine.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.VM.Types; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Neo.VM +{ + /// + /// Represents the VM used to execute the script. + /// + public class ExecutionEngine : IDisposable + { + private VMState state = VMState.BREAK; + internal bool isJumping = false; + + public JumpTable JumpTable { get; } + + /// + /// Restrictions on the VM. + /// + public ExecutionEngineLimits Limits { get; } + + /// + /// Used for reference counting of objects in the VM. + /// + public ReferenceCounter ReferenceCounter { get; } + + /// + /// The invocation stack of the VM. + /// + public Stack InvocationStack { get; } = new Stack(); + + /// + /// The top frame of the invocation stack. + /// + public ExecutionContext? CurrentContext { get; private set; } + + /// + /// The bottom frame of the invocation stack. + /// + public ExecutionContext? EntryContext { get; private set; } + + /// + /// The stack to store the return values. + /// + public EvaluationStack ResultStack { get; } + + /// + /// The VM object representing the uncaught exception. + /// + public StackItem? UncaughtException { get; internal set; } + + /// + /// The current state of the VM. + /// + public VMState State + { + get + { + return state; + } + protected internal set + { + if (state != value) + { + state = value; + OnStateChanged(); + } + } + } + + /// + /// Initializes a new instance of the class. + /// + public ExecutionEngine(JumpTable? jumpTable = null) : this(jumpTable, new ReferenceCounter(), ExecutionEngineLimits.Default) + { + } + + /// + /// Initializes a new instance of the class with the specified and . + /// + /// The jump table to be used. + /// The reference counter to be used. + /// Restrictions on the VM. + protected ExecutionEngine(JumpTable? jumpTable, ReferenceCounter referenceCounter, ExecutionEngineLimits limits) + { + JumpTable = jumpTable ?? JumpTable.Default; + Limits = limits; + ReferenceCounter = referenceCounter; + ResultStack = new EvaluationStack(referenceCounter); + } + + public virtual void Dispose() + { + InvocationStack.Clear(); + } + + /// + /// Start execution of the VM. + /// + /// + public virtual VMState Execute() + { + if (State == VMState.BREAK) + State = VMState.NONE; + while (State != VMState.HALT && State != VMState.FAULT) + ExecuteNext(); + return State; + } + + /// + /// Execute the next instruction. + /// + protected internal void ExecuteNext() + { + if (InvocationStack.Count == 0) + { + State = VMState.HALT; + } + else + { + try + { + ExecutionContext context = CurrentContext!; + Instruction instruction = context.CurrentInstruction ?? Instruction.RET; + PreExecuteInstruction(instruction); +#if VMPERF + Console.WriteLine("op:[" + + this.CurrentContext.InstructionPointer.ToString("X04") + + "]" + + this.CurrentContext.CurrentInstruction?.OpCode + + " " + + this.CurrentContext.EvaluationStack); +#endif + try + { + JumpTable[instruction.OpCode](this, instruction); + } + catch (CatchableException ex) when (Limits.CatchEngineExceptions) + { + JumpTable.ExecuteThrow(this, ex.Message); + } + PostExecuteInstruction(instruction); + if (!isJumping) context.MoveNext(); + isJumping = false; + } + catch (Exception e) + { + OnFault(e); + } + } + } + + /// + /// Loads the specified context into the invocation stack. + /// + /// The context to load. + public virtual void LoadContext(ExecutionContext context) + { + if (InvocationStack.Count >= Limits.MaxInvocationStackSize) + throw new InvalidOperationException($"MaxInvocationStackSize exceed: {InvocationStack.Count}"); + InvocationStack.Push(context); + if (EntryContext is null) EntryContext = context; + CurrentContext = context; + } + + /// + /// Called when a context is unloaded. + /// + /// The context being unloaded. + internal virtual void UnloadContext(ExecutionContext context) + { + if (InvocationStack.Count == 0) + { + CurrentContext = null; + EntryContext = null; + } + else + { + CurrentContext = InvocationStack.Peek(); + } + if (context.StaticFields != null && context.StaticFields != CurrentContext?.StaticFields) + { + context.StaticFields.ClearReferences(); + } + context.LocalVariables?.ClearReferences(); + context.Arguments?.ClearReferences(); + } + + /// + /// Create a new context with the specified script without loading. + /// + /// The script used to create the context. + /// The number of values that the context should return when it is unloaded. + /// The pointer indicating the current instruction. + /// The created context. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected ExecutionContext CreateContext(Script script, int rvcount, int initialPosition) + { + return new ExecutionContext(script, rvcount, ReferenceCounter) + { + InstructionPointer = initialPosition + }; + } + + /// + /// Create a new context with the specified script and load it. + /// + /// The script used to create the context. + /// The number of values that the context should return when it is unloaded. + /// The pointer indicating the current instruction. + /// The created context. + public ExecutionContext LoadScript(Script script, int rvcount = -1, int initialPosition = 0) + { + ExecutionContext context = CreateContext(script, rvcount, initialPosition); + LoadContext(context); + return context; + } + + /// + /// Called when an exception that cannot be caught by the VM is thrown. + /// + /// The exception that caused the state. + protected virtual void OnFault(Exception ex) + { + State = VMState.FAULT; + } + + /// + /// Called when the state of the VM changed. + /// + protected virtual void OnStateChanged() + { + } + + /// + /// Returns the item at the specified index from the top of the current stack without removing it. + /// + /// The index of the object from the top of the stack. + /// The item at the specified index. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public StackItem Peek(int index = 0) + { + return CurrentContext!.EvaluationStack.Peek(index); + } + + /// + /// Removes and returns the item at the top of the current stack. + /// + /// The item removed from the top of the stack. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public StackItem Pop() + { + return CurrentContext!.EvaluationStack.Pop(); + } + + /// + /// Removes and returns the item at the top of the current stack and convert it to the specified type. + /// + /// The type to convert to. + /// The item removed from the top of the stack. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Pop() where T : StackItem + { + return CurrentContext!.EvaluationStack.Pop(); + } + + /// + /// Called after an instruction is executed. + /// + protected virtual void PostExecuteInstruction(Instruction instruction) + { + if (ReferenceCounter.Count < Limits.MaxStackSize) return; + if (ReferenceCounter.CheckZeroReferred() > Limits.MaxStackSize) + throw new InvalidOperationException($"MaxStackSize exceed: {ReferenceCounter.Count}"); + } + + /// + /// Called before an instruction is executed. + /// + protected virtual void PreExecuteInstruction(Instruction instruction) { } + + /// + /// Pushes an item onto the top of the current stack. + /// + /// The item to be pushed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Push(StackItem item) + { + CurrentContext!.EvaluationStack.Push(item); + } + } +} diff --git a/src/Neo.VM/ExecutionEngineLimits.cs b/src/Neo.VM/ExecutionEngineLimits.cs new file mode 100644 index 0000000000..6670572fb4 --- /dev/null +++ b/src/Neo.VM/ExecutionEngineLimits.cs @@ -0,0 +1,88 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ExecutionEngineLimits.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Runtime.CompilerServices; + +namespace Neo.VM +{ + /// + /// Represents the restrictions on the VM. + /// + public sealed record ExecutionEngineLimits + { + /// + /// The default strategy. + /// + public static readonly ExecutionEngineLimits Default = new(); + + /// + /// The maximum number of bits that and can shift. + /// + public int MaxShift { get; init; } = 256; + + /// + /// The maximum number of items that can be contained in the VM's evaluation stacks and slots. + /// + public uint MaxStackSize { get; init; } = 2 * 1024; + + /// + /// The maximum size of an item in the VM. + /// + public uint MaxItemSize { get; init; } = ushort.MaxValue * 2; + + /// + /// The largest comparable size. If a or exceeds this size, comparison operations on it cannot be performed in the VM. + /// + public uint MaxComparableSize { get; init; } = 65536; + + /// + /// The maximum number of frames in the invocation stack of the VM. + /// + public uint MaxInvocationStackSize { get; init; } = 1024; + + /// + /// The maximum nesting depth of -- blocks. + /// + public uint MaxTryNestingDepth { get; init; } = 16; + + /// + /// Allow to catch the ExecutionEngine Exceptions + /// + public bool CatchEngineExceptions { get; init; } = true; + + /// + /// Assert that the size of the item meets the limit. + /// + /// The size to be checked. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AssertMaxItemSize(int size) + { + if (size < 0 || size > MaxItemSize) + { + throw new InvalidOperationException($"MaxItemSize exceed: {size}"); + } + } + + /// + /// Assert that the number of bits shifted meets the limit. + /// + /// The number of bits shifted. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void AssertShift(int shift) + { + if (shift > MaxShift || shift < 0) + { + throw new InvalidOperationException($"Invalid shift value: {shift}"); + } + } + } +} diff --git a/src/Neo.VM/GlobalSuppressions.cs b/src/Neo.VM/GlobalSuppressions.cs new file mode 100644 index 0000000000..acf6e71c9c --- /dev/null +++ b/src/Neo.VM/GlobalSuppressions.cs @@ -0,0 +1,19 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// GlobalSuppressions.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Usage", "CA1816")] diff --git a/src/Neo.VM/Instruction.cs b/src/Neo.VM/Instruction.cs new file mode 100644 index 0000000000..f7f9d2d791 --- /dev/null +++ b/src/Neo.VM/Instruction.cs @@ -0,0 +1,235 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Instruction.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Buffers.Binary; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; + +namespace Neo.VM +{ + /// + /// Represents instructions in the VM script. + /// + [DebuggerDisplay("OpCode={OpCode}")] + public class Instruction + { + /// + /// Represents the instruction with . + /// + public static Instruction RET { get; } = new Instruction(OpCode.RET); + + /// + /// The of the instruction. + /// + public readonly OpCode OpCode; + + /// + /// The operand of the instruction. + /// + public readonly ReadOnlyMemory Operand; + + private static readonly int[] OperandSizePrefixTable = new int[256]; + private static readonly int[] OperandSizeTable = new int[256]; + + /// + /// Gets the size of the instruction. + /// + public int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + int prefixSize = OperandSizePrefixTable[(int)OpCode]; + return prefixSize > 0 + ? 1 + prefixSize + Operand.Length + : 1 + OperandSizeTable[(int)OpCode]; + } + } + + /// + /// Gets the first operand as . + /// + public short TokenI16 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return BinaryPrimitives.ReadInt16LittleEndian(Operand.Span); + } + } + + /// + /// Gets the first operand as . + /// + public int TokenI32 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return BinaryPrimitives.ReadInt32LittleEndian(Operand.Span); + } + } + + /// + /// Gets the second operand as . + /// + public int TokenI32_1 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return BinaryPrimitives.ReadInt32LittleEndian(Operand.Span[4..]); + } + } + + /// + /// Gets the first operand as . + /// + public sbyte TokenI8 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return (sbyte)Operand.Span[0]; + } + } + + /// + /// Gets the second operand as . + /// + public sbyte TokenI8_1 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return (sbyte)Operand.Span[1]; + } + } + + /// + /// Gets the operand as . + /// + public string TokenString + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return Encoding.ASCII.GetString(Operand.Span); + } + } + + /// + /// Gets the first operand as . + /// + public ushort TokenU16 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return BinaryPrimitives.ReadUInt16LittleEndian(Operand.Span); + } + } + + /// + /// Gets the first operand as . + /// + public uint TokenU32 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return BinaryPrimitives.ReadUInt32LittleEndian(Operand.Span); + } + } + + /// + /// Gets the first operand as . + /// + public byte TokenU8 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return Operand.Span[0]; + } + } + + /// + /// Gets the second operand as . + /// + public byte TokenU8_1 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return Operand.Span[1]; + } + } + + static Instruction() + { + foreach (FieldInfo field in typeof(OpCode).GetFields(BindingFlags.Public | BindingFlags.Static)) + { + OperandSizeAttribute? attribute = field.GetCustomAttribute(); + if (attribute == null) continue; + int index = (int)(OpCode)field.GetValue(null)!; + OperandSizePrefixTable[index] = attribute.SizePrefix; + OperandSizeTable[index] = attribute.Size; + } + } + + private Instruction(OpCode opcode) + { + OpCode = opcode; + if (!Enum.IsDefined(typeof(OpCode), opcode)) throw new BadScriptException(); + } + + internal Instruction(ReadOnlyMemory script, int ip) : this((OpCode)script.Span[ip++]) + { + ReadOnlySpan span = script.Span; + int operandSizePrefix = OperandSizePrefixTable[(int)OpCode]; + int operandSize = 0; + switch (operandSizePrefix) + { + case 0: + operandSize = OperandSizeTable[(int)OpCode]; + break; + case 1: + if (ip >= span.Length) + throw new BadScriptException($"Instruction out of bounds. InstructionPointer: {ip}"); + operandSize = span[ip]; + break; + case 2: + if (ip + 1 >= span.Length) + throw new BadScriptException($"Instruction out of bounds. InstructionPointer: {ip}"); + operandSize = BinaryPrimitives.ReadUInt16LittleEndian(span[ip..]); + break; + case 4: + if (ip + 3 >= span.Length) + throw new BadScriptException($"Instruction out of bounds. InstructionPointer: {ip}"); + operandSize = BinaryPrimitives.ReadInt32LittleEndian(span[ip..]); + if (operandSize < 0) + throw new BadScriptException($"Instruction out of bounds. InstructionPointer: {ip}, operandSize: {operandSize}"); + break; + } + ip += operandSizePrefix; + if (operandSize > 0) + { + if (ip + operandSize > script.Length) + throw new BadScriptException($"Instrucion out of bounds. InstructionPointer: {ip}, operandSize: {operandSize}, length: {script.Length}"); + Operand = script.Slice(ip, operandSize); + } + } + } +} diff --git a/src/Neo.VM/JumpTable/JumpTable.Bitwisee.cs b/src/Neo.VM/JumpTable/JumpTable.Bitwisee.cs new file mode 100644 index 0000000000..51fcd9c6f2 --- /dev/null +++ b/src/Neo.VM/JumpTable/JumpTable.Bitwisee.cs @@ -0,0 +1,110 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// JumpTable.Bitwisee.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Runtime.CompilerServices; + +namespace Neo.VM +{ + /// + /// Partial class for performing bitwise and logical operations on integers within a jump table. + /// + public partial class JumpTable + { + /// + /// Flips all of the bits of an integer. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 1, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Invert(ExecutionEngine engine, Instruction instruction) + { + var x = engine.Pop().GetInteger(); + engine.Push(~x); + } + + /// + /// Computes the bitwise AND of two integers. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void And(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + engine.Push(x1 & x2); + } + + /// + /// Computes the bitwise OR of two integers. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Or(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + engine.Push(x1 | x2); + } + + /// + /// Computes the bitwise XOR (exclusive OR) of two integers. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void XOr(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + engine.Push(x1 ^ x2); + } + + /// + /// Determines whether two objects are equal according to the execution engine's comparison rules. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Equal(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop(); + var x1 = engine.Pop(); + engine.Push(x1.Equals(x2, engine.Limits)); + } + + /// + /// Determines whether two objects are not equal according to the execution engine's comparison rules. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void NotEqual(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop(); + var x1 = engine.Pop(); + engine.Push(!x1.Equals(x2, engine.Limits)); + } + } +} diff --git a/src/Neo.VM/JumpTable/JumpTable.Compound.cs b/src/Neo.VM/JumpTable/JumpTable.Compound.cs new file mode 100644 index 0000000000..aeb2047825 --- /dev/null +++ b/src/Neo.VM/JumpTable/JumpTable.Compound.cs @@ -0,0 +1,552 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// JumpTable.Compound.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.VM.Types; +using System; +using System.Linq; +using System.Numerics; +using System.Runtime.CompilerServices; +using VMArray = Neo.VM.Types.Array; + +namespace Neo.VM +{ + /// + /// Partial class for manipulating compound types like maps, arrays, and structs within a jump table. + /// + public partial class JumpTable + { + /// + /// Packs a map from the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2n+1, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PackMap(ExecutionEngine engine, Instruction instruction) + { + var size = (int)engine.Pop().GetInteger(); + if (size < 0 || size * 2 > engine.CurrentContext!.EvaluationStack.Count) + throw new InvalidOperationException($"The value {size} is out of range."); + Map map = new(engine.ReferenceCounter); + for (var i = 0; i < size; i++) + { + var key = engine.Pop(); + var value = engine.Pop(); + map[key] = value; + } + engine.Push(map); + } + + /// + /// Packs a struct from the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop n+1, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PackStruct(ExecutionEngine engine, Instruction instruction) + { + var size = (int)engine.Pop().GetInteger(); + if (size < 0 || size > engine.CurrentContext!.EvaluationStack.Count) + throw new InvalidOperationException($"The value {size} is out of range."); + Struct @struct = new(engine.ReferenceCounter); + for (var i = 0; i < size; i++) + { + var item = engine.Pop(); + @struct.Add(item); + } + engine.Push(@struct); + } + + /// + /// Packs an array from the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop n+1, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Pack(ExecutionEngine engine, Instruction instruction) + { + var size = (int)engine.Pop().GetInteger(); + if (size < 0 || size > engine.CurrentContext!.EvaluationStack.Count) + throw new InvalidOperationException($"The value {size} is out of range."); + VMArray array = new(engine.ReferenceCounter); + for (var i = 0; i < size; i++) + { + var item = engine.Pop(); + array.Add(item); + } + engine.Push(array); + } + + /// + /// Unpacks a compound type from the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 1, Push 2n+1 or n+1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Unpack(ExecutionEngine engine, Instruction instruction) + { + var compound = engine.Pop(); + switch (compound) + { + case Map map: + foreach (var (key, value) in map.Reverse()) + { + engine.Push(value); + engine.Push(key); + } + break; + case VMArray array: + for (var i = array.Count - 1; i >= 0; i--) + { + engine.Push(array[i]); + } + break; + default: + throw new InvalidOperationException($"Invalid type for {instruction.OpCode}: {compound.Type}"); + } + engine.Push(compound.Count); + } + + /// + /// Creates a new empty array with zero elements on the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// + /// Pop 0, Push 1 + /// TODO: Change to NewNullArray method or add it? + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void NewArray0(ExecutionEngine engine, Instruction instruction) + { + engine.Push(new VMArray(engine.ReferenceCounter)); + } + + /// + /// Creates a new array with a specified number of elements on the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 1, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void NewArray(ExecutionEngine engine, Instruction instruction) + { + var n = (int)engine.Pop().GetInteger(); + if (n < 0 || n > engine.Limits.MaxStackSize) + throw new InvalidOperationException($"MaxStackSize exceed: {n}"); + + engine.Push(new VMArray(engine.ReferenceCounter, Enumerable.Repeat(StackItem.Null, n))); + } + + /// + /// Creates a new array with a specified number of elements and a specified type on the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 1, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void NewArray_T(ExecutionEngine engine, Instruction instruction) + { + var n = (int)engine.Pop().GetInteger(); + if (n < 0 || n > engine.Limits.MaxStackSize) + throw new InvalidOperationException($"MaxStackSize exceed: {n}"); + + var type = (StackItemType)instruction.TokenU8; + if (!Enum.IsDefined(typeof(StackItemType), type)) + throw new InvalidOperationException($"Invalid type for {instruction.OpCode}: {instruction.TokenU8}"); + + var item = instruction.TokenU8 switch + { + (byte)StackItemType.Boolean => StackItem.False, + (byte)StackItemType.Integer => Integer.Zero, + (byte)StackItemType.ByteString => ByteString.Empty, + _ => StackItem.Null + }; + + engine.Push(new VMArray(engine.ReferenceCounter, Enumerable.Repeat(item, n))); + } + + /// + /// Creates a new empty struct with zero elements on the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 0, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void NewStruct0(ExecutionEngine engine, Instruction instruction) + { + engine.Push(new Struct(engine.ReferenceCounter)); + } + + /// + /// Creates a new struct with a specified number of elements on the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 1, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void NewStruct(ExecutionEngine engine, Instruction instruction) + { + var n = (int)engine.Pop().GetInteger(); + if (n < 0 || n > engine.Limits.MaxStackSize) + throw new InvalidOperationException($"MaxStackSize exceed: {n}"); + Struct result = new(engine.ReferenceCounter); + for (var i = 0; i < n; i++) + result.Add(StackItem.Null); + engine.Push(result); + } + + /// + /// Creates a new empty map on the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 0, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void NewMap(ExecutionEngine engine, Instruction instruction) + { + engine.Push(new Map(engine.ReferenceCounter)); + } + + /// + /// Gets the size of the top item on the evaluation stack and pushes it onto the stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 1, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Size(ExecutionEngine engine, Instruction instruction) + { + var x = engine.Pop(); + switch (x) + { + case CompoundType compound: + engine.Push(compound.Count); + break; + case PrimitiveType primitive: + engine.Push(primitive.Size); + break; + case Types.Buffer buffer: + engine.Push(buffer.Size); + break; + default: + throw new InvalidOperationException($"Invalid type for {instruction.OpCode}: {x.Type}"); + } + } + + /// + /// Checks whether the top item on the evaluation stack has the specified key. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void HasKey(ExecutionEngine engine, Instruction instruction) + { + var key = engine.Pop(); + var x = engine.Pop(); + // Check the type of the top item and perform the corresponding action. + switch (x) + { + // For arrays, check if the index is within bounds and push the result onto the stack. + case VMArray array: + { + // TODO: Overflow and underflow checking needs to be done. + var index = (int)key.GetInteger(); + if (index < 0) + throw new InvalidOperationException($"The negative value {index} is invalid for OpCode.{instruction.OpCode}."); + engine.Push(index < array.Count); + break; + } + // For maps, check if the key exists and push the result onto the stack. + case Map map: + { + engine.Push(map.ContainsKey(key)); + break; + } + // For buffers, check if the index is within bounds and push the result onto the stack. + case Types.Buffer buffer: + { + // TODO: Overflow and underflow checking needs to be done. + var index = (int)key.GetInteger(); + if (index < 0) + throw new InvalidOperationException($"The negative value {index} is invalid for OpCode.{instruction.OpCode}."); + engine.Push(index < buffer.Size); + break; + } + // For byte strings, check if the index is within bounds and push the result onto the stack. + case ByteString array: + { + // TODO: Overflow and underflow checking needs to be done. + var index = (int)key.GetInteger(); + if (index < 0) + throw new InvalidOperationException($"The negative value {index} is invalid for OpCode.{instruction.OpCode}."); + engine.Push(index < array.Size); + break; + } + default: + throw new InvalidOperationException($"Invalid type for {instruction.OpCode}: {x.Type}"); + } + } + + /// + /// Retrieves the keys of a map and pushes them onto the evaluation stack as an array. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 1, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Keys(ExecutionEngine engine, Instruction instruction) + { + var map = engine.Pop(); + engine.Push(new VMArray(engine.ReferenceCounter, map.Keys)); + } + + /// + /// Retrieves the values of a compound type and pushes them onto the evaluation stack as an array. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 1, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Values(ExecutionEngine engine, Instruction instruction) + { + var x = engine.Pop(); + var values = x switch + { + VMArray array => array, + Map map => map.Values, + _ => throw new InvalidOperationException($"Invalid type for {instruction.OpCode}: {x.Type}"), + }; + VMArray newArray = new(engine.ReferenceCounter); + foreach (var item in values) + if (item is Struct s) + newArray.Add(s.Clone(engine.Limits)); + else + newArray.Add(item); + engine.Push(newArray); + } + + /// + /// Retrieves the item from an array, map, buffer, or byte string based on the specified key, + /// and pushes it onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PickItem(ExecutionEngine engine, Instruction instruction) + { + var key = engine.Pop(); + var x = engine.Pop(); + switch (x) + { + case VMArray array: + { + var index = (int)key.GetInteger(); + if (index < 0 || index >= array.Count) + throw new CatchableException($"The value {index} is out of range."); + engine.Push(array[index]); + break; + } + case Map map: + { + if (!map.TryGetValue(key, out var value)) + throw new CatchableException($"Key not found in {nameof(Map)}"); + engine.Push(value); + break; + } + case PrimitiveType primitive: + { + var byteArray = primitive.GetSpan(); + var index = (int)key.GetInteger(); + if (index < 0 || index >= byteArray.Length) + throw new CatchableException($"The value {index} is out of range."); + engine.Push((BigInteger)byteArray[index]); + break; + } + case Types.Buffer buffer: + { + var index = (int)key.GetInteger(); + if (index < 0 || index >= buffer.Size) + throw new CatchableException($"The value {index} is out of range."); + engine.Push((BigInteger)buffer.InnerBuffer.Span[index]); + break; + } + default: + throw new InvalidOperationException($"Invalid type for {instruction.OpCode}: {x.Type}"); + } + } + + /// + /// Appends an item to the end of the specified array. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 1, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Append(ExecutionEngine engine, Instruction instruction) + { + var newItem = engine.Pop(); + var array = engine.Pop(); + if (newItem is Struct s) newItem = s.Clone(engine.Limits); + array.Add(newItem); + } + + /// + /// A value v, index n (or key) and an array (or map) are taken from main stack. Attribution array[n]=v (or map[n]=v) is performed. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 3, Push 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void SetItem(ExecutionEngine engine, Instruction instruction) + { + var value = engine.Pop(); + if (value is Struct s) value = s.Clone(engine.Limits); + var key = engine.Pop(); + var x = engine.Pop(); + switch (x) + { + case VMArray array: + { + var index = (int)key.GetInteger(); + if (index < 0 || index >= array.Count) + throw new CatchableException($"The value {index} is out of range."); + array[index] = value; + break; + } + case Map map: + { + map[key] = value; + break; + } + case Types.Buffer buffer: + { + var index = (int)key.GetInteger(); + if (index < 0 || index >= buffer.Size) + throw new CatchableException($"The value {index} is out of range."); + if (value is not PrimitiveType p) + throw new InvalidOperationException($"Value must be a primitive type in {instruction.OpCode}"); + var b = (int)p.GetInteger(); + if (b < sbyte.MinValue || b > byte.MaxValue) + throw new InvalidOperationException($"Overflow in {instruction.OpCode}, {b} is not a byte type."); + buffer.InnerBuffer.Span[index] = (byte)b; + break; + } + default: + throw new InvalidOperationException($"Invalid type for {instruction.OpCode}: {x.Type}"); + } + } + + /// + /// Reverses the order of items in the specified array or buffer. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 1, Push 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void ReverseItems(ExecutionEngine engine, Instruction instruction) + { + var x = engine.Pop(); + switch (x) + { + case VMArray array: + array.Reverse(); + break; + case Types.Buffer buffer: + buffer.InnerBuffer.Span.Reverse(); + break; + default: + throw new InvalidOperationException($"Invalid type for {instruction.OpCode}: {x.Type}"); + } + } + + /// + /// Removes the item at the specified index from the array or map. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2, Push 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Remove(ExecutionEngine engine, Instruction instruction) + { + var key = engine.Pop(); + var x = engine.Pop(); + switch (x) + { + case VMArray array: + var index = (int)key.GetInteger(); + if (index < 0 || index >= array.Count) + throw new InvalidOperationException($"The value {index} is out of range."); + array.RemoveAt(index); + break; + case Map map: + map.Remove(key); + break; + default: + throw new InvalidOperationException($"Invalid type for {instruction.OpCode}: {x.Type}"); + } + } + + /// + /// Clears all items from the compound type. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 1, Push 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void ClearItems(ExecutionEngine engine, Instruction instruction) + { + var x = engine.Pop(); + x.Clear(); + } + + /// + /// Removes and returns the item at the top of the specified array. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 1, Push 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PopItem(ExecutionEngine engine, Instruction instruction) + { + var x = engine.Pop(); + var index = x.Count - 1; + engine.Push(x[index]); + x.RemoveAt(index); + } + } +} diff --git a/src/Neo.VM/JumpTable/JumpTable.Control.cs b/src/Neo.VM/JumpTable/JumpTable.Control.cs new file mode 100644 index 0000000000..3c8448c050 --- /dev/null +++ b/src/Neo.VM/JumpTable/JumpTable.Control.cs @@ -0,0 +1,700 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// JumpTable.Control.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.VM.Types; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Neo.VM +{ + /// + /// Partial class for performing bitwise and logical operations on integers within a jump table. + /// + /// + /// For binary operations x1 and x2, x1 is the first pushed onto the evaluation stack (the second popped from the stack), + /// x2 is the second pushed onto the evaluation stack (the first popped from the stack) + /// + public partial class JumpTable + { + /// + /// No operation. Does nothing. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Nop(ExecutionEngine engine, Instruction instruction) + { + } + + /// + /// Jumps to the specified offset from the current instruction pointer, + /// where the offset is obtained from the first operand of the instruction and interpreted as a signed byte. + /// + /// + /// The execution engine. + /// The instruction containing the offset as the first operand. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Jmp(ExecutionEngine engine, Instruction instruction) + { + ExecuteJumpOffset(engine, instruction.TokenI8); + } + + /// + /// Jumps to the specified offset from the current instruction pointer, + /// where the offset is obtained from the first operand of the instruction and interpreted as a 32-bit signed integer. + /// + /// + /// The execution engine. + /// The instruction containing the offset as the first operand. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Jmp_L(ExecutionEngine engine, Instruction instruction) + { + ExecuteJumpOffset(engine, instruction.TokenI32); + } + + /// + /// Jumps to the specified offset from the current instruction pointer + /// if the boolean result of popping the evaluation stack is true. + /// The offset is obtained from the instruction's first operand interpreted as a signed byte. + /// + /// + /// The execution engine. + /// The instruction containing the offset as the first operand. + /// Pop 1, Push 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JmpIf(ExecutionEngine engine, Instruction instruction) + { + if (engine.Pop().GetBoolean()) + ExecuteJumpOffset(engine, instruction.TokenI8); + } + + /// + /// Jumps to the specified offset from the current instruction pointer + /// if the boolean result of popping the evaluation stack is true. + /// The offset is obtained from the instruction's first operand interpreted as a 32-bit signed integer. + /// + /// + /// The execution engine. + /// The instruction containing the offset as the first operand. + /// Pop 1, Push 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JmpIf_L(ExecutionEngine engine, Instruction instruction) + { + if (engine.Pop().GetBoolean()) + ExecuteJumpOffset(engine, instruction.TokenI32); + } + + /// + /// Jumps to the specified offset from the current instruction pointer + /// if the boolean result of popping the evaluation stack is false. + /// The offset is obtained from the instruction's first operand interpreted as a signed byte. + /// + /// + /// The execution engine. + /// The instruction containing the offset as the first operand. + /// Pop 1, Push 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JmpIfNot(ExecutionEngine engine, Instruction instruction) + { + if (!engine.Pop().GetBoolean()) + ExecuteJumpOffset(engine, instruction.TokenI8); + } + + /// + /// Jumps to the specified offset from the current instruction pointer + /// if the boolean result of popping the evaluation stack is false. + /// The offset is obtained from the instruction's first operand interpreted as a 32-bit signed integer. + /// + /// + /// The execution engine. + /// The instruction containing the offset as the first operand. + /// Pop 1, Push 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JmpIfNot_L(ExecutionEngine engine, Instruction instruction) + { + if (!engine.Pop().GetBoolean()) + ExecuteJumpOffset(engine, instruction.TokenI32); + } + + /// + /// Jumps to the specified offset from the current instruction pointer + /// if the two integers popped from the evaluation stack are equal. + /// The offset is obtained from the instruction's first operand interpreted as a signed byte. + /// + /// + /// The execution engine. + /// The instruction containing the offset as the first operand. + /// Pop 2, Push 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JmpEq(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + if (x1 == x2) + ExecuteJumpOffset(engine, instruction.TokenI8); + } + + /// + /// Jumps to the specified offset from the current instruction pointer + /// if the two integers popped from the evaluation stack are equal. + /// The offset is obtained from the instruction's first operand interpreted as a 32-bit signed integer. + /// + /// + /// The execution engine. + /// The instruction containing the offset as the first operand. + /// Pop 2, Push 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JmpEq_L(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + if (x1 == x2) + ExecuteJumpOffset(engine, instruction.TokenI32); + } + + /// + /// Jumps to the specified offset from the current instruction pointer + /// if the two integers popped from the evaluation stack are not equal. + /// The offset is obtained from the instruction's first operand interpreted as a signed byte. + /// + /// + /// The execution engine. + /// The instruction containing the offset as the first operand. + /// Pop 2, Push 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JmpNe(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + if (x1 != x2) + ExecuteJumpOffset(engine, instruction.TokenI8); + } + + /// + /// Jumps to the specified offset from the current instruction pointer + /// if the two integers popped from the evaluation stack are not equal. + /// The offset is obtained from the instruction's first operand interpreted as a 32-bit signed integer. + /// + /// + /// The execution engine. + /// The instruction containing the offset as the first operand. + /// Pop 2, Push 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JmpNe_L(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + if (x1 != x2) + ExecuteJumpOffset(engine, instruction.TokenI32); + } + + /// + /// Jumps to the specified offset from the current instruction pointer + /// if the first integer pushed onto the evaluation stack is greater than the second integer. + /// The offset is obtained from the instruction's first operand interpreted as a signed byte. + /// + /// + /// The execution engine. + /// The instruction containing the offset as the first operand. + /// Pop 2, Push 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JmpGt(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + if (x1 > x2) + ExecuteJumpOffset(engine, instruction.TokenI8); + } + + /// + /// Jumps to the specified offset from the current instruction pointer + /// if the first integer pushed onto the evaluation stack is greater than the second integer. + /// The offset is obtained from the instruction's first operand interpreted as a 32-bit signed integer. + /// + /// + /// The execution engine. + /// The instruction containing the offset as the first operand. + /// Pop 2, Push 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JmpGt_L(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + if (x1 > x2) + ExecuteJumpOffset(engine, instruction.TokenI32); + } + + /// + /// Jumps to the specified offset from the current instruction pointer + /// if the first integer pushed onto the evaluation stack is greater than or equal to the second integer. + /// The offset is obtained from the instruction's first operand interpreted as a signed byte. + /// + /// + /// The execution engine. + /// The instruction containing the offset as the first operand. + /// Pop 2, Push 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JmpGe(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + if (x1 >= x2) + ExecuteJumpOffset(engine, instruction.TokenI8); + } + + /// + /// Jumps to the specified offset from the current instruction pointer + /// if the first integer pushed onto the evaluation stack is greater than or equal to the second integer. + /// The offset is obtained from the instruction's first operand interpreted as a 32-bit signed integer. + /// + /// + /// The execution engine. + /// The instruction containing the offset as the first operand. + /// Pop 2, Push 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JmpGe_L(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + if (x1 >= x2) + ExecuteJumpOffset(engine, instruction.TokenI32); + } + + /// + /// Jumps to the specified offset from the current instruction pointer + /// if the first integer pushed onto the evaluation stack is less than the second integer. + /// The offset is obtained from the instruction's first operand interpreted as a signed byte. + /// + /// + /// The execution engine. + /// The instruction containing the offset as the first operand. + /// Pop 2, Push 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JmpLt(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + if (x1 < x2) + ExecuteJumpOffset(engine, instruction.TokenI8); + } + + /// + /// Jumps to the specified offset from the current instruction pointer + /// if the first integer pushed onto the evaluation stack is less than the second integer. + /// The offset is obtained from the instruction's first operand interpreted as a 32-bit signed integer. + /// + /// + /// The execution engine. + /// The instruction containing the offset as the first operand. + /// Pop 2, Push 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JmpLt_L(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + if (x1 < x2) + ExecuteJumpOffset(engine, instruction.TokenI32); + } + + /// + /// Jumps to the specified offset from the current instruction pointer + /// if the first integer pushed onto the evaluation stack is less than or equal to the second integer. + /// The offset is obtained from the instruction's first operand interpreted as a signed byte. + /// + /// + /// The execution engine. + /// The instruction containing the offset as the first operand. + /// Pop 2, Push 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JmpLe(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + if (x1 <= x2) + ExecuteJumpOffset(engine, instruction.TokenI8); + } + + /// + /// Jumps to the specified offset from the current instruction pointer + /// if the first integer pushed onto the evaluation stack is less than or equal to the second integer. + /// The offset is obtained from the instruction's first operand interpreted as a 32-bit signed integer. + /// + /// + /// The execution engine. + /// The instruction containing the offset as the first operand. + /// Pop 2, Push 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void JmpLe_L(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + if (x1 <= x2) + ExecuteJumpOffset(engine, instruction.TokenI32); + } + + /// + /// Calls a method specified by the offset from the current instruction pointer. + /// The offset is obtained from the instruction's first operand interpreted as a signed byte. + /// + /// + /// The execution engine. + /// The instruction containing the offset as the first operand. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Call(ExecutionEngine engine, Instruction instruction) + { + ExecuteCall(engine, checked(engine.CurrentContext!.InstructionPointer + instruction.TokenI8)); + } + + /// + /// Calls a method specified by the offset from the current instruction pointer. + /// The offset is obtained from the instruction's first operand interpreted as a 32-bit signed integer. + /// + /// + /// The execution engine. + /// The instruction containing the offset as the first operand. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Call_L(ExecutionEngine engine, Instruction instruction) + { + ExecuteCall(engine, checked(engine.CurrentContext!.InstructionPointer + instruction.TokenI32)); + } + + /// + /// Calls a method specified by the pointer pushed onto the evaluation stack. + /// It verifies if the pointer belongs to the current script. + /// + /// + /// The execution engine. + /// The current instruction. + /// Pop 1, Push 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void CallA(ExecutionEngine engine, Instruction instruction) + { + var x = engine.Pop(); + if (x.Script != engine.CurrentContext!.Script) + throw new InvalidOperationException("Pointers can't be shared between scripts"); + ExecuteCall(engine, x.Position); + } + + /// + /// Calls the function described by the token. + /// + /// + /// The execution engine. + /// The current instruction. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void CallT(ExecutionEngine engine, Instruction instruction) + { + throw new InvalidOperationException($"Token not found: {instruction.TokenU16}"); + } + + /// + /// Aborts the execution by turning the virtual machine state to FAULT immediately, and the exception cannot be caught. + /// + /// + /// The execution engine. + /// The current instruction. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Abort(ExecutionEngine engine, Instruction instruction) + { + throw new Exception($"{OpCode.ABORT} is executed."); + } + + /// + /// Pop the top value of the stack. If it's false, exit vm execution and set vm state to FAULT. + /// + /// + /// The execution engine. + /// The current instruction. + /// Pop 1, Push 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Assert(ExecutionEngine engine, Instruction instruction) + { + var x = engine.Pop().GetBoolean(); + if (!x) + throw new Exception($"{OpCode.ASSERT} is executed with false result."); + } + + /// + /// Pop the top value of the stack, and throw it. + /// + /// + /// The execution engine. + /// The current instruction. + /// Pop 1, Push 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Throw(ExecutionEngine engine, Instruction instruction) + { + ExecuteThrow(engine, engine.Pop()); + } + + /// + /// Initiates a try block with the specified catch and finally offsets. + /// If there's no catch block, set CatchOffset to 0. If there's no finally block, set FinallyOffset to 0. + /// where the catch offset is obtained from the first operand of the instruction and interpreted as a signed byte, + /// the catch offset is obtained from the second operand of the instruction and interpreted as a signed byte. + /// + /// + /// The execution engine. + /// The current instruction. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Try(ExecutionEngine engine, Instruction instruction) + { + int catchOffset = instruction.TokenI8; + int finallyOffset = instruction.TokenI8_1; + ExecuteTry(engine, catchOffset, finallyOffset); + } + + /// + /// Initiates a try block with the specified catch and finally offsets. + /// If there's no catch block, set CatchOffset to 0. If there's no finally block, set FinallyOffset to 0. + /// where the catch offset is obtained from the first operand of the instruction and interpreted as a 32-bit signed integer, + /// the catch offset is obtained from the second operand of the instruction and interpreted as a 32-bit signed integer. + /// + /// + /// The execution engine. + /// The current instruction. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Try_L(ExecutionEngine engine, Instruction instruction) + { + var catchOffset = instruction.TokenI32; + var finallyOffset = instruction.TokenI32_1; + ExecuteTry(engine, catchOffset, finallyOffset); + } + + /// + /// Ensures that the appropriate surrounding finally blocks are executed, + /// then unconditionally transfers control to the specific target instruction represented as a 1-byte signed offset + /// from the beginning of the current instruction. + /// + /// + /// The execution engine. + /// The current instruction. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void EndTry(ExecutionEngine engine, Instruction instruction) + { + var endOffset = instruction.TokenI8; + ExecuteEndTry(engine, endOffset); + } + + /// + /// Ensures that the appropriate surrounding finally blocks are executed, + /// then unconditionally transfers control to the specific target instruction represented as a 4-byte signed offset + /// from the beginning of the current instruction. + /// + /// + /// The execution engine. + /// The current instruction. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void EndTry_L(ExecutionEngine engine, Instruction instruction) + { + var endOffset = instruction.TokenI32; + ExecuteEndTry(engine, endOffset); + } + + /// + /// Ends the finally block. If no exception occurs or is caught, + /// the VM jumps to the target instruction specified by ENDTRY/ENDTRY_L. + /// Otherwise, the VM rethrows the exception to the upper layer. + /// + /// + /// The execution engine. + /// The current instruction. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void EndFinally(ExecutionEngine engine, Instruction instruction) + { + if (engine.CurrentContext!.TryStack is null) + throw new InvalidOperationException($"The corresponding TRY block cannot be found."); + if (!engine.CurrentContext.TryStack.TryPop(out var currentTry)) + throw new InvalidOperationException($"The corresponding TRY block cannot be found."); + + if (engine.UncaughtException is null) + engine.CurrentContext.InstructionPointer = currentTry.EndPointer; + else + ExecuteThrow(engine, engine.UncaughtException); + + engine.isJumping = true; + } + + /// + /// Returns from the current method. + /// + /// + /// The execution engine. + /// The current instruction. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Ret(ExecutionEngine engine, Instruction instruction) + { + var context_pop = engine.InvocationStack.Pop(); + var stack_eval = engine.InvocationStack.Count == 0 ? engine.ResultStack : engine.InvocationStack.Peek().EvaluationStack; + if (context_pop.EvaluationStack != stack_eval) + { + if (context_pop.RVCount >= 0 && context_pop.EvaluationStack.Count != context_pop.RVCount) + throw new InvalidOperationException("RVCount doesn't match with EvaluationStack"); + context_pop.EvaluationStack.CopyTo(stack_eval); + } + if (engine.InvocationStack.Count == 0) + engine.State = VMState.HALT; + engine.UnloadContext(context_pop); + engine.isJumping = true; + } + + /// + /// Calls to an interop service. + /// + /// + /// The execution engine. + /// The current instruction. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Syscall(ExecutionEngine engine, Instruction instruction) + { + throw new InvalidOperationException($"Syscall not found: {instruction.TokenU32}"); + } + + #region Execute methods + + /// + /// Executes a call operation by loading a new execution context at the specified position. + /// + /// The execution engine. + /// The position to load the new execution context. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void ExecuteCall(ExecutionEngine engine, int position) + { + engine.LoadContext(engine.CurrentContext!.Clone(position)); + } + + /// + /// Executes the end of a try block, either popping it from the try stack or transitioning to the finally block. + /// + /// The execution engine. + /// The offset to the end of the try block. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void ExecuteEndTry(ExecutionEngine engine, int endOffset) + { + if (engine.CurrentContext!.TryStack is null) + throw new InvalidOperationException($"The corresponding TRY block cannot be found."); + if (!engine.CurrentContext.TryStack.TryPeek(out var currentTry)) + throw new InvalidOperationException($"The corresponding TRY block cannot be found."); + if (currentTry.State == ExceptionHandlingState.Finally) + throw new InvalidOperationException($"The opcode {OpCode.ENDTRY} can't be executed in a FINALLY block."); + + var endPointer = checked(engine.CurrentContext.InstructionPointer + endOffset); + if (currentTry.HasFinally) + { + currentTry.State = ExceptionHandlingState.Finally; + currentTry.EndPointer = endPointer; + engine.CurrentContext.InstructionPointer = currentTry.FinallyPointer; + } + else + { + engine.CurrentContext.TryStack.Pop(); + engine.CurrentContext.InstructionPointer = endPointer; + } + engine.isJumping = true; + } + + /// + /// Executes a jump operation to the specified position. + /// + /// The execution engine. + /// The position to jump to. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void ExecuteJump(ExecutionEngine engine, int position) + { + if (position < 0 || position >= engine.CurrentContext!.Script.Length) + throw new ArgumentOutOfRangeException($"Jump out of range for position: {position}"); + engine.CurrentContext.InstructionPointer = position; + engine.isJumping = true; + } + + /// + /// Executes a jump operation with the specified offset from the current instruction pointer. + /// + /// The execution engine. + /// The offset from the current instruction pointer. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void ExecuteJumpOffset(ExecutionEngine engine, int offset) + { + ExecuteJump(engine, checked(engine.CurrentContext!.InstructionPointer + offset)); + } + + /// + /// Executes a try block operation with the specified catch and finally offsets. + /// + /// The execution engine. + /// The catch block offset. + /// The finally block offset. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void ExecuteTry(ExecutionEngine engine, int catchOffset, int finallyOffset) + { + if (catchOffset == 0 && finallyOffset == 0) + throw new InvalidOperationException($"catchOffset and finallyOffset can't be 0 in a TRY block"); + if (engine.CurrentContext!.TryStack is null) + engine.CurrentContext.TryStack = new Stack(); + else if (engine.CurrentContext.TryStack.Count >= engine.Limits.MaxTryNestingDepth) + throw new InvalidOperationException("MaxTryNestingDepth exceed."); + var catchPointer = catchOffset == 0 ? -1 : checked(engine.CurrentContext.InstructionPointer + catchOffset); + var finallyPointer = finallyOffset == 0 ? -1 : checked(engine.CurrentContext.InstructionPointer + finallyOffset); + engine.CurrentContext.TryStack.Push(new ExceptionHandlingContext(catchPointer, finallyPointer)); + } + + /// + /// Executes a throw operation, handling any surrounding try-catch-finally blocks. + /// + /// The execution engine. + /// The exception to throw. + public virtual void ExecuteThrow(ExecutionEngine engine, StackItem? ex) + { + engine.UncaughtException = ex; + + var pop = 0; + foreach (var executionContext in engine.InvocationStack) + { + if (executionContext.TryStack != null) + { + while (executionContext.TryStack.TryPeek(out var tryContext)) + { + if (tryContext.State == ExceptionHandlingState.Finally || (tryContext.State == ExceptionHandlingState.Catch && !tryContext.HasFinally)) + { + executionContext.TryStack.Pop(); + continue; + } + for (var i = 0; i < pop; i++) + { + engine.UnloadContext(engine.InvocationStack.Pop()); + } + if (tryContext.State == ExceptionHandlingState.Try && tryContext.HasCatch) + { + tryContext.State = ExceptionHandlingState.Catch; + engine.Push(engine.UncaughtException!); + executionContext.InstructionPointer = tryContext.CatchPointer; + engine.UncaughtException = null; + } + else + { + tryContext.State = ExceptionHandlingState.Finally; + executionContext.InstructionPointer = tryContext.FinallyPointer; + } + engine.isJumping = true; + return; + } + } + ++pop; + } + + throw new VMUnhandledException(engine.UncaughtException!); + } + + #endregion + } +} diff --git a/src/Neo.VM/JumpTable/JumpTable.Numeric.cs b/src/Neo.VM/JumpTable/JumpTable.Numeric.cs new file mode 100644 index 0000000000..989991a07c --- /dev/null +++ b/src/Neo.VM/JumpTable/JumpTable.Numeric.cs @@ -0,0 +1,477 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// JumpTable.Numeric.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Neo.VM +{ + /// + /// Partial class for mathematical operations within a jump table. + /// + /// + /// For binary operations x1 and x2, x1 is the first pushed onto the evaluation stack (the second popped from the stack), + /// x2 is the second pushed onto the evaluation stack (the first popped from the stack) + /// + public partial class JumpTable + { + /// + /// Computes the sign of the specified integer. + /// If the value is negative, puts -1; if positive, puts 1; if zero, puts 0. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 1, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Sign(ExecutionEngine engine, Instruction instruction) + { + var x = engine.Pop().GetInteger(); + engine.Push(x.Sign); + } + + /// + /// Computes the absolute value of the specified integer. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 1, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Abs(ExecutionEngine engine, Instruction instruction) + { + var x = engine.Pop().GetInteger(); + engine.Push(BigInteger.Abs(x)); + } + + /// + /// Computes the negation of the specified integer. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 1, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Negate(ExecutionEngine engine, Instruction instruction) + { + var x = engine.Pop().GetInteger(); + engine.Push(-x); + } + + /// + /// Increments the specified integer by one. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 1, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Inc(ExecutionEngine engine, Instruction instruction) + { + var x = engine.Pop().GetInteger(); + engine.Push(x + 1); + } + + /// + /// Decrements the specified integer by one. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 1, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Dec(ExecutionEngine engine, Instruction instruction) + { + var x = engine.Pop().GetInteger(); + engine.Push(x - 1); + } + + /// + /// Computes the sum of two integers. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Add(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + engine.Push(x1 + x2); + } + + /// + /// Computes the difference between two integers. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Sub(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + engine.Push(x1 - x2); + } + + /// + /// Computes the product of two integers. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Mul(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + engine.Push(x1 * x2); + } + + /// + /// Computes the quotient of two integers. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Div(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + engine.Push(x1 / x2); + } + + /// + /// Computes the result of raising a number to the specified power. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Mod(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + engine.Push(x1 % x2); + } + + /// + /// Computes the square root of the specified integer. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Pow(ExecutionEngine engine, Instruction instruction) + { + var exponent = (int)engine.Pop().GetInteger(); + engine.Limits.AssertShift(exponent); + var value = engine.Pop().GetInteger(); + engine.Push(BigInteger.Pow(value, exponent)); + } + + /// + /// + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 1, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Sqrt(ExecutionEngine engine, Instruction instruction) + { + engine.Push(engine.Pop().GetInteger().Sqrt()); + } + + /// + /// Computes the modular multiplication of two integers. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 3, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void ModMul(ExecutionEngine engine, Instruction instruction) + { + var modulus = engine.Pop().GetInteger(); + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + engine.Push(x1 * x2 % modulus); + } + + /// + /// Computes the modular exponentiation of an integer. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 3, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void ModPow(ExecutionEngine engine, Instruction instruction) + { + var modulus = engine.Pop().GetInteger(); + var exponent = engine.Pop().GetInteger(); + var value = engine.Pop().GetInteger(); + var result = exponent == -1 + ? value.ModInverse(modulus) + : BigInteger.ModPow(value, exponent, modulus); + engine.Push(result); + } + + /// + /// Computes the left shift of an integer. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Shl(ExecutionEngine engine, Instruction instruction) + { + var shift = (int)engine.Pop().GetInteger(); + engine.Limits.AssertShift(shift); + if (shift == 0) return; + var x = engine.Pop().GetInteger(); + engine.Push(x << shift); + } + + /// + /// Computes the right shift of an integer. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Shr(ExecutionEngine engine, Instruction instruction) + { + var shift = (int)engine.Pop().GetInteger(); + engine.Limits.AssertShift(shift); + if (shift == 0) return; + var x = engine.Pop().GetInteger(); + engine.Push(x >> shift); + } + + /// + /// If the input is 0 or 1, it is flipped. Otherwise the output will be 0. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 1, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Not(ExecutionEngine engine, Instruction instruction) + { + var x = engine.Pop().GetBoolean(); + engine.Push(!x); + } + + /// + /// Computes the logical AND of the top two stack items and pushes the result onto the stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void BoolAnd(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetBoolean(); + var x1 = engine.Pop().GetBoolean(); + engine.Push(x1 && x2); + } + + /// + /// Computes the logical OR of the top two stack items and pushes the result onto the stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void BoolOr(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetBoolean(); + var x1 = engine.Pop().GetBoolean(); + engine.Push(x1 || x2); + } + + /// + /// Determines whether the top stack item is not zero and pushes the result onto the stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 1, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Nz(ExecutionEngine engine, Instruction instruction) + { + var x = engine.Pop().GetInteger(); + engine.Push(!x.IsZero); + } + + /// + /// Determines whether the top two stack items are equal and pushes the result onto the stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void NumEqual(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + engine.Push(x1 == x2); + } + + /// + /// Determines whether the top two stack items are not equal and pushes the result onto the stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void NumNotEqual(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + engine.Push(x1 != x2); + } + + /// + /// Determines whether the two integer at the top of the stack, x1 are less than x2, and pushes the result onto the stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Lt(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop(); + var x1 = engine.Pop(); + if (x1.IsNull || x2.IsNull) + engine.Push(false); + else + engine.Push(x1.GetInteger() < x2.GetInteger()); + } + + /// + /// Determines whether the two integer at the top of the stack, x1 are less than or equal to x2, and pushes the result onto the stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Le(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop(); + var x1 = engine.Pop(); + if (x1.IsNull || x2.IsNull) + engine.Push(false); + else + engine.Push(x1.GetInteger() <= x2.GetInteger()); + } + + /// + /// Determines whether the two integer at the top of the stack, x1 are greater than x2, and pushes the result onto the stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Gt(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop(); + var x1 = engine.Pop(); + if (x1.IsNull || x2.IsNull) + engine.Push(false); + else + engine.Push(x1.GetInteger() > x2.GetInteger()); + } + + /// + /// Determines whether the two integer at the top of the stack, x1 are greater than or equal to x2, and pushes the result onto the stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Ge(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop(); + var x1 = engine.Pop(); + if (x1.IsNull || x2.IsNull) + engine.Push(false); + else + engine.Push(x1.GetInteger() >= x2.GetInteger()); + } + + /// + /// Computes the minimum of the top two stack items and pushes the result onto the stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Min(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + engine.Push(BigInteger.Min(x1, x2)); + } + + /// + /// Computes the maximum of the top two stack items and pushes the result onto the stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Max(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetInteger(); + var x1 = engine.Pop().GetInteger(); + engine.Push(BigInteger.Max(x1, x2)); + } + + /// + /// Determines whether the top stack item is within the range specified by the next two top stack items + /// and pushes the result onto the stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 3, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Within(ExecutionEngine engine, Instruction instruction) + { + var b = engine.Pop().GetInteger(); + var a = engine.Pop().GetInteger(); + var x = engine.Pop().GetInteger(); + engine.Push(a <= x && x < b); + } + } +} diff --git a/src/Neo.VM/JumpTable/JumpTable.Push.cs b/src/Neo.VM/JumpTable/JumpTable.Push.cs new file mode 100644 index 0000000000..c0f1429b4d --- /dev/null +++ b/src/Neo.VM/JumpTable/JumpTable.Push.cs @@ -0,0 +1,406 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// JumpTable.Push.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.VM.Types; +using System; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Neo.VM +{ + /// + /// Partial class for providing methods to push various data types onto the evaluation stack within a jump table. + /// + /// Pop 0, Push 1 + public partial class JumpTable + { + /// + /// Pushes an 8-bit signed integer onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PushInt8(ExecutionEngine engine, Instruction instruction) + { + engine.Push(new BigInteger(instruction.Operand.Span)); + } + + /// + /// Pushes an 16-bit signed integer onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PushInt16(ExecutionEngine engine, Instruction instruction) + { + engine.Push(new BigInteger(instruction.Operand.Span)); + } + + /// + /// Pushes an 32-bit signed integer onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PushInt32(ExecutionEngine engine, Instruction instruction) + { + engine.Push(new BigInteger(instruction.Operand.Span)); + } + + /// + /// Pushes an 64-bit signed integer onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PushInt64(ExecutionEngine engine, Instruction instruction) + { + engine.Push(new BigInteger(instruction.Operand.Span)); + } + + /// + /// Pushes an 128-bit signed integer onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PushInt128(ExecutionEngine engine, Instruction instruction) + { + engine.Push(new BigInteger(instruction.Operand.Span)); + } + + /// + /// Pushes an 256-bit signed integer onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PushInt256(ExecutionEngine engine, Instruction instruction) + { + engine.Push(new BigInteger(instruction.Operand.Span)); + } + + /// + /// Pushes a boolean value of true onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PushT(ExecutionEngine engine, Instruction instruction) + { + engine.Push(StackItem.True); + } + + /// + /// Pushes a boolean value of false onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PushF(ExecutionEngine engine, Instruction instruction) + { + engine.Push(StackItem.False); + } + + /// + /// Pushes the address of the specified instruction onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PushA(ExecutionEngine engine, Instruction instruction) + { + var position = checked(engine.CurrentContext!.InstructionPointer + instruction.TokenI32); + if (position < 0 || position > engine.CurrentContext.Script.Length) + throw new InvalidOperationException($"Bad pointer address(Instruction instruction) {position}"); + engine.Push(new Pointer(engine.CurrentContext.Script, position)); + } + + /// + /// Pushes a null onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PushNull(ExecutionEngine engine, Instruction instruction) + { + engine.Push(StackItem.Null); + } + + /// + /// Pushes a byte array with a length prefix onto the evaluation stack. + /// The length of the array is 1 byte. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PushData1(ExecutionEngine engine, Instruction instruction) + { + engine.Limits.AssertMaxItemSize(instruction.Operand.Length); + engine.Push(instruction.Operand); + } + + /// + /// Pushes a byte array with a length prefix onto the evaluation stack. + /// The length of the array is 1 bytes. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PushData2(ExecutionEngine engine, Instruction instruction) + { + engine.Limits.AssertMaxItemSize(instruction.Operand.Length); + engine.Push(instruction.Operand); + } + + /// + /// Pushes a byte array with a length prefix onto the evaluation stack. + /// The length of the array is 4 bytes. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PushData4(ExecutionEngine engine, Instruction instruction) + { + engine.Limits.AssertMaxItemSize(instruction.Operand.Length); + engine.Push(instruction.Operand); + } + + /// + /// Pushes the integer value of -1 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void PushM1(ExecutionEngine engine, Instruction instruction) + { + engine.Push(-1); + } + + /// + /// Pushes the integer value of 0 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Push0(ExecutionEngine engine, Instruction instruction) + { + engine.Push(0); + } + + /// + /// Pushes the integer value of 1 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Push1(ExecutionEngine engine, Instruction instruction) + { + engine.Push(1); + } + + /// + /// Pushes the integer value of 2 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Push2(ExecutionEngine engine, Instruction instruction) + { + engine.Push(2); + } + + /// + /// Pushes the integer value of 3 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Push3(ExecutionEngine engine, Instruction instruction) + { + engine.Push(3); + } + + /// + /// Pushes the integer value of 4 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Push4(ExecutionEngine engine, Instruction instruction) + { + engine.Push(4); + } + + /// + /// Pushes the integer value of 5 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Push5(ExecutionEngine engine, Instruction instruction) + { + engine.Push(5); + } + + /// + /// Pushes the integer value of 6 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Push6(ExecutionEngine engine, Instruction instruction) + { + engine.Push(6); + } + + /// + /// Pushes the integer value of 7 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Push7(ExecutionEngine engine, Instruction instruction) + { + engine.Push(7); + } + + /// + /// Pushes the integer value of 8 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Push8(ExecutionEngine engine, Instruction instruction) + { + engine.Push(8); + } + + /// + /// Pushes the integer value of 9 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Push9(ExecutionEngine engine, Instruction instruction) + { + engine.Push(9); + } + + /// + /// Pushes the integer value of 10 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Push10(ExecutionEngine engine, Instruction instruction) + { + engine.Push(10); + } + + /// + /// Pushes the integer value of 11 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Push11(ExecutionEngine engine, Instruction instruction) + { + engine.Push(11); + } + + /// + /// Pushes the integer value of 12 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Push12(ExecutionEngine engine, Instruction instruction) + { + engine.Push(12); + } + + /// + /// Pushes the integer value of 13 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Push13(ExecutionEngine engine, Instruction instruction) + { + engine.Push(13); + } + + /// + /// Pushes the integer value of 14 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Push14(ExecutionEngine engine, Instruction instruction) + { + engine.Push(14); + } + + /// + /// Pushes the integer value of 15 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Push15(ExecutionEngine engine, Instruction instruction) + { + engine.Push(15); + } + + /// + /// Pushes the integer value of 16 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Push16(ExecutionEngine engine, Instruction instruction) + { + engine.Push(16); + } + } +} diff --git a/src/Neo.VM/JumpTable/JumpTable.Slot.cs b/src/Neo.VM/JumpTable/JumpTable.Slot.cs new file mode 100644 index 0000000000..22214924cf --- /dev/null +++ b/src/Neo.VM/JumpTable/JumpTable.Slot.cs @@ -0,0 +1,683 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// JumpTable.Slot.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.VM.Types; +using System; +using System.Runtime.CompilerServices; + +namespace Neo.VM +{ + /// + /// Partial class representing a jump table for executing specific operations related to slot manipulation. + /// + public partial class JumpTable + { + /// + /// Initializes the static field slot in the current execution context. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void InitSSlot(ExecutionEngine engine, Instruction instruction) + { + if (engine.CurrentContext!.StaticFields != null) + throw new InvalidOperationException($"{instruction.OpCode} cannot be executed twice."); + if (instruction.TokenU8 == 0) + throw new InvalidOperationException($"The operand {instruction.TokenU8} is invalid for OpCode.{instruction.OpCode}."); + engine.CurrentContext.StaticFields = new Slot(instruction.TokenU8, engine.ReferenceCounter); + } + + /// + /// Initializes the local variable slot or the argument slot in the current execution context. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void InitSlot(ExecutionEngine engine, Instruction instruction) + { + if (engine.CurrentContext!.LocalVariables != null || engine.CurrentContext.Arguments != null) + throw new InvalidOperationException($"{instruction.OpCode} cannot be executed twice."); + if (instruction.TokenU16 == 0) + throw new InvalidOperationException($"The operand {instruction.TokenU16} is invalid for OpCode.{instruction.OpCode}."); + if (instruction.TokenU8 > 0) + { + engine.CurrentContext.LocalVariables = new Slot(instruction.TokenU8, engine.ReferenceCounter); + } + if (instruction.TokenU8_1 > 0) + { + var items = new StackItem[instruction.TokenU8_1]; + for (var i = 0; i < instruction.TokenU8_1; i++) + { + items[i] = engine.Pop(); + } + engine.CurrentContext.Arguments = new Slot(items, engine.ReferenceCounter); + } + } + + /// + /// Loads the value at index 0 from the static field slot onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void LdSFld0(ExecutionEngine engine, Instruction instruction) + { + ExecuteLoadFromSlot(engine, engine.CurrentContext!.StaticFields, 0); + } + + /// + /// Loads the value at index 1 from the static field slot onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void LdSFld1(ExecutionEngine engine, Instruction instruction) + { + ExecuteLoadFromSlot(engine, engine.CurrentContext!.StaticFields, 1); + } + + /// + /// Loads the value at index 2 from the static field slot onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void LdSFld2(ExecutionEngine engine, Instruction instruction) + { + ExecuteLoadFromSlot(engine, engine.CurrentContext!.StaticFields, 2); + } + + /// + /// Loads the value at index 3 from the static field slot onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void LdSFld3(ExecutionEngine engine, Instruction instruction) + { + ExecuteLoadFromSlot(engine, engine.CurrentContext!.StaticFields, 3); + } + + /// + /// Loads the value at index 4 from the static field slot onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void LdSFld4(ExecutionEngine engine, Instruction instruction) + { + ExecuteLoadFromSlot(engine, engine.CurrentContext!.StaticFields, 4); + } + + /// + /// Loads the value at index 5 from the static field slot onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void LdSFld5(ExecutionEngine engine, Instruction instruction) + { + ExecuteLoadFromSlot(engine, engine.CurrentContext!.StaticFields, 5); + } + + /// + /// Loads the value at index 6 from the static field slot onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void LdSFld6(ExecutionEngine engine, Instruction instruction) + { + ExecuteLoadFromSlot(engine, engine.CurrentContext!.StaticFields, 6); + } + + /// + /// Loads the static field at a specified index onto the evaluation stack. + /// The index is represented as a 1-byte unsigned integer. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void LdSFld(ExecutionEngine engine, Instruction instruction) + { + ExecuteLoadFromSlot(engine, engine.CurrentContext!.StaticFields, instruction.TokenU8); + } + + /// + /// Stores the value at index 0 from the evaluation stack into the static field slot. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void StSFld0(ExecutionEngine engine, Instruction instruction) + { + ExecuteStoreToSlot(engine, engine.CurrentContext!.StaticFields, 0); + } + + /// + /// Stores the value at index 1 from the evaluation stack into the static field slot. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void StSFld1(ExecutionEngine engine, Instruction instruction) + { + ExecuteStoreToSlot(engine, engine.CurrentContext!.StaticFields, 1); + } + + /// + /// Stores the value at index 2 from the evaluation stack into the static field slot. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void StSFld2(ExecutionEngine engine, Instruction instruction) + { + ExecuteStoreToSlot(engine, engine.CurrentContext!.StaticFields, 2); + } + + /// + /// Stores the value at index 3 from the evaluation stack into the static field slot. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void StSFld3(ExecutionEngine engine, Instruction instruction) + { + ExecuteStoreToSlot(engine, engine.CurrentContext!.StaticFields, 3); + } + + /// + /// Stores the value at index 4 from the evaluation stack into the static field slot. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void StSFld4(ExecutionEngine engine, Instruction instruction) + { + ExecuteStoreToSlot(engine, engine.CurrentContext!.StaticFields, 4); + } + + /// + /// Stores the value at index 5 from the evaluation stack into the static field slot. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void StSFld5(ExecutionEngine engine, Instruction instruction) + { + ExecuteStoreToSlot(engine, engine.CurrentContext!.StaticFields, 5); + } + + /// + /// Stores the value at index 6 from the evaluation stack into the static field slot. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void StSFld6(ExecutionEngine engine, Instruction instruction) + { + ExecuteStoreToSlot(engine, engine.CurrentContext!.StaticFields, 6); + } + + /// + /// Stores the value on top of the evaluation stack in the static field list at a specified index. + /// The index is represented as a 1-byte unsigned integer. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void StSFld(ExecutionEngine engine, Instruction instruction) + { + ExecuteStoreToSlot(engine, engine.CurrentContext!.StaticFields, instruction.TokenU8); + } + + /// + /// Loads the local variable at index 0 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void LdLoc0(ExecutionEngine engine, Instruction instruction) + { + ExecuteLoadFromSlot(engine, engine.CurrentContext!.LocalVariables, 0); + } + + /// + /// Loads the local variable at index 1 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void LdLoc1(ExecutionEngine engine, Instruction instruction) + { + ExecuteLoadFromSlot(engine, engine.CurrentContext!.LocalVariables, 1); + } + + /// + /// Loads the local variable at index 2 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void LdLoc2(ExecutionEngine engine, Instruction instruction) + { + ExecuteLoadFromSlot(engine, engine.CurrentContext!.LocalVariables, 2); + } + + /// + /// Loads the local variable at index 3 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void LdLoc3(ExecutionEngine engine, Instruction instruction) + { + ExecuteLoadFromSlot(engine, engine.CurrentContext!.LocalVariables, 3); + } + + /// + /// Loads the local variable at index 4 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void LdLoc4(ExecutionEngine engine, Instruction instruction) + { + ExecuteLoadFromSlot(engine, engine.CurrentContext!.LocalVariables, 4); + } + + /// + /// Loads the local variable at index 5 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void LdLoc5(ExecutionEngine engine, Instruction instruction) + { + ExecuteLoadFromSlot(engine, engine.CurrentContext!.LocalVariables, 5); + } + + /// + /// Loads the local variable at index 6 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void LdLoc6(ExecutionEngine engine, Instruction instruction) + { + ExecuteLoadFromSlot(engine, engine.CurrentContext!.LocalVariables, 6); + } + + /// + /// Loads the local variable at a specified index onto the evaluation stack. + /// The index is represented as a 1-byte unsigned integer. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void LdLoc(ExecutionEngine engine, Instruction instruction) + { + ExecuteLoadFromSlot(engine, engine.CurrentContext!.LocalVariables, instruction.TokenU8); + } + + /// + /// Stores the value on top of the evaluation stack in the local variable list at index 0. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void StLoc0(ExecutionEngine engine, Instruction instruction) + { + ExecuteStoreToSlot(engine, engine.CurrentContext!.LocalVariables, 0); + } + + /// + /// Stores the value on top of the evaluation stack in the local variable list at index 1. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void StLoc1(ExecutionEngine engine, Instruction instruction) + { + ExecuteStoreToSlot(engine, engine.CurrentContext!.LocalVariables, 1); + } + + /// + /// Stores the value on top of the evaluation stack in the local variable list at index 2. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void StLoc2(ExecutionEngine engine, Instruction instruction) + { + ExecuteStoreToSlot(engine, engine.CurrentContext!.LocalVariables, 2); + } + + /// + /// Stores the value on top of the evaluation stack in the local variable list at index 3. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void StLoc3(ExecutionEngine engine, Instruction instruction) + { + ExecuteStoreToSlot(engine, engine.CurrentContext!.LocalVariables, 3); + } + + /// + /// Stores the value on top of the evaluation stack in the local variable list at index 4. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void StLoc4(ExecutionEngine engine, Instruction instruction) + { + ExecuteStoreToSlot(engine, engine.CurrentContext!.LocalVariables, 4); + } + + /// + /// Stores the value on top of the evaluation stack in the local variable list at index 5. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void StLoc5(ExecutionEngine engine, Instruction instruction) + { + ExecuteStoreToSlot(engine, engine.CurrentContext!.LocalVariables, 5); + } + + /// + /// Stores the value on top of the evaluation stack in the local variable list at index 6. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void StLoc6(ExecutionEngine engine, Instruction instruction) + { + ExecuteStoreToSlot(engine, engine.CurrentContext!.LocalVariables, 6); + } + + /// + /// Stores the value on top of the evaluation stack in the local variable list at a specified index. + /// The index is represented as a 1-byte unsigned integer. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void StLoc(ExecutionEngine engine, Instruction instruction) + { + ExecuteStoreToSlot(engine, engine.CurrentContext!.LocalVariables, instruction.TokenU8); + } + + /// + /// Loads the argument at index 0 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void LdArg0(ExecutionEngine engine, Instruction instruction) + { + ExecuteLoadFromSlot(engine, engine.CurrentContext!.Arguments, 0); + } + + /// + /// Loads the argument at index 1 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void LdArg1(ExecutionEngine engine, Instruction instruction) + { + ExecuteLoadFromSlot(engine, engine.CurrentContext!.Arguments, 1); + } + + /// + /// Loads the argument at index 2 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void LdArg2(ExecutionEngine engine, Instruction instruction) + { + ExecuteLoadFromSlot(engine, engine.CurrentContext!.Arguments, 2); + } + + /// + /// Loads the argument at index 3 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void LdArg3(ExecutionEngine engine, Instruction instruction) + { + ExecuteLoadFromSlot(engine, engine.CurrentContext!.Arguments, 3); + } + + /// + /// Loads the argument at index 4 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void LdArg4(ExecutionEngine engine, Instruction instruction) + { + ExecuteLoadFromSlot(engine, engine.CurrentContext!.Arguments, 4); + } + + /// + /// Loads the argument at index 5 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void LdArg5(ExecutionEngine engine, Instruction instruction) + { + ExecuteLoadFromSlot(engine, engine.CurrentContext!.Arguments, 5); + } + + /// + /// Loads the argument at index 6 onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void LdArg6(ExecutionEngine engine, Instruction instruction) + { + ExecuteLoadFromSlot(engine, engine.CurrentContext!.Arguments, 6); + } + + /// + /// Loads the argument at a specified index onto the evaluation stack. + /// The index is represented as a 1-byte unsigned integer. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void LdArg(ExecutionEngine engine, Instruction instruction) + { + ExecuteLoadFromSlot(engine, engine.CurrentContext!.Arguments, instruction.TokenU8); + } + + /// + /// Stores the value on top of the evaluation stack in the argument slot at index 0. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void StArg0(ExecutionEngine engine, Instruction instruction) + { + ExecuteStoreToSlot(engine, engine.CurrentContext!.Arguments, 0); + } + + /// + /// Stores the value on top of the evaluation stack in the argument slot at index 1. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void StArg1(ExecutionEngine engine, Instruction instruction) + { + ExecuteStoreToSlot(engine, engine.CurrentContext!.Arguments, 1); + } + + /// + /// Stores the value on top of the evaluation stack in the argument slot at index 2. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void StArg2(ExecutionEngine engine, Instruction instruction) + { + ExecuteStoreToSlot(engine, engine.CurrentContext!.Arguments, 2); + } + + /// + /// Stores the value on top of the evaluation stack in the argument slot at index 3. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void StArg3(ExecutionEngine engine, Instruction instruction) + { + ExecuteStoreToSlot(engine, engine.CurrentContext!.Arguments, 3); + } + + /// + /// Stores the value on top of the evaluation stack in the argument slot at index 4. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void StArg4(ExecutionEngine engine, Instruction instruction) + { + ExecuteStoreToSlot(engine, engine.CurrentContext!.Arguments, 4); + } + + /// + /// Stores the value on top of the evaluation stack in the argument slot at index 5. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void StArg5(ExecutionEngine engine, Instruction instruction) + { + ExecuteStoreToSlot(engine, engine.CurrentContext!.Arguments, 5); + } + + /// + /// Stores the value on top of the evaluation stack in the argument slot at index 6. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void StArg6(ExecutionEngine engine, Instruction instruction) + { + ExecuteStoreToSlot(engine, engine.CurrentContext!.Arguments, 6); + } + + /// + /// Stores the value on top of the evaluation stack in the argument slot at a specified index. + /// The index is represented as a 1-byte unsigned integer. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void StArg(ExecutionEngine engine, Instruction instruction) + { + ExecuteStoreToSlot(engine, engine.CurrentContext!.Arguments, instruction.TokenU8); + } + + #region Execute methods + + /// + /// Executes the store operation into the specified slot at the given index. + /// + /// The execution engine. + /// The slot to store the value. + /// The index within the slot. + public virtual void ExecuteStoreToSlot(ExecutionEngine engine, Slot? slot, int index) + { + if (slot is null) + throw new InvalidOperationException("Slot has not been initialized."); + if (index < 0 || index >= slot.Count) + throw new InvalidOperationException($"Index out of range when storing to slot: {index}"); + slot[index] = engine.Pop(); + } + + /// + /// Executes the load operation from the specified slot at the given index onto the evaluation stack. + /// + /// The execution engine. + /// The slot to load the value from. + /// The index within the slot. + public virtual void ExecuteLoadFromSlot(ExecutionEngine engine, Slot? slot, int index) + { + if (slot is null) + throw new InvalidOperationException("Slot has not been initialized."); + if (index < 0 || index >= slot.Count) + throw new InvalidOperationException($"Index out of range when loading from slot: {index}"); + engine.Push(slot[index]); + } + + #endregion + } +} diff --git a/src/Neo.VM/JumpTable/JumpTable.Splice.cs b/src/Neo.VM/JumpTable/JumpTable.Splice.cs new file mode 100644 index 0000000000..2f49ea0107 --- /dev/null +++ b/src/Neo.VM/JumpTable/JumpTable.Splice.cs @@ -0,0 +1,151 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// JumpTable.Splice.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Runtime.CompilerServices; + +namespace Neo.VM +{ + /// + /// Partial class representing a jump table for executing specific operations related to string manipulation. + /// + public partial class JumpTable + { + /// + /// Creates a new buffer with the specified length and pushes it onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 1, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void NewBuffer(ExecutionEngine engine, Instruction instruction) + { + int length = (int)engine.Pop().GetInteger(); + engine.Limits.AssertMaxItemSize(length); + engine.Push(new Types.Buffer(length)); + } + + /// + /// Copies a specified number of bytes from one buffer to another buffer. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 5, Push 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Memcpy(ExecutionEngine engine, Instruction instruction) + { + int count = (int)engine.Pop().GetInteger(); + if (count < 0) + throw new InvalidOperationException($"The value {count} is out of range."); + int si = (int)engine.Pop().GetInteger(); + if (si < 0) + throw new InvalidOperationException($"The value {si} is out of range."); + ReadOnlySpan src = engine.Pop().GetSpan(); + if (checked(si + count) > src.Length) + throw new InvalidOperationException($"The value {count} is out of range."); + int di = (int)engine.Pop().GetInteger(); + if (di < 0) + throw new InvalidOperationException($"The value {di} is out of range."); + Types.Buffer dst = engine.Pop(); + if (checked(di + count) > dst.Size) + throw new InvalidOperationException($"The value {count} is out of range."); + src.Slice(si, count).CopyTo(dst.InnerBuffer.Span[di..]); + } + + /// + /// Concatenates two buffers and pushes the result onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Cat(ExecutionEngine engine, Instruction instruction) + { + var x2 = engine.Pop().GetSpan(); + var x1 = engine.Pop().GetSpan(); + int length = x1.Length + x2.Length; + engine.Limits.AssertMaxItemSize(length); + Types.Buffer result = new(length, false); + x1.CopyTo(result.InnerBuffer.Span); + x2.CopyTo(result.InnerBuffer.Span[x1.Length..]); + engine.Push(result); + } + + /// + /// Extracts a substring from the specified buffer and pushes it onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 3, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void SubStr(ExecutionEngine engine, Instruction instruction) + { + int count = (int)engine.Pop().GetInteger(); + if (count < 0) + throw new InvalidOperationException($"The value {count} is out of range."); + int index = (int)engine.Pop().GetInteger(); + if (index < 0) + throw new InvalidOperationException($"The value {index} is out of range."); + var x = engine.Pop().GetSpan(); + if (index + count > x.Length) + throw new InvalidOperationException($"The value {count} is out of range."); + Types.Buffer result = new(count, false); + x.Slice(index, count).CopyTo(result.InnerBuffer.Span); + engine.Push(result); + } + + /// + /// Extracts a specified number of characters from the left side of the buffer and pushes them onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Left(ExecutionEngine engine, Instruction instruction) + { + int count = (int)engine.Pop().GetInteger(); + if (count < 0) + throw new InvalidOperationException($"The value {count} is out of range."); + var x = engine.Pop().GetSpan(); + if (count > x.Length) + throw new InvalidOperationException($"The value {count} is out of range."); + Types.Buffer result = new(count, false); + x[..count].CopyTo(result.InnerBuffer.Span); + engine.Push(result); + } + + /// + /// Extracts a specified number of characters from the right side of the buffer and pushes them onto the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Right(ExecutionEngine engine, Instruction instruction) + { + int count = (int)engine.Pop().GetInteger(); + if (count < 0) + throw new InvalidOperationException($"The value {count} is out of range."); + var x = engine.Pop().GetSpan(); + if (count > x.Length) + throw new InvalidOperationException($"The value {count} is out of range."); + Types.Buffer result = new(count, false); + x[^count..^0].CopyTo(result.InnerBuffer.Span); + engine.Push(result); + } + } +} diff --git a/src/Neo.VM/JumpTable/JumpTable.Stack.cs b/src/Neo.VM/JumpTable/JumpTable.Stack.cs new file mode 100644 index 0000000000..45ee5565a7 --- /dev/null +++ b/src/Neo.VM/JumpTable/JumpTable.Stack.cs @@ -0,0 +1,227 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// JumpTable.Stack.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.VM.Types; +using System; +using System.Runtime.CompilerServices; + +namespace Neo.VM +{ + /// + /// Partial class for stack manipulation within a jump table in the execution engine. + /// + public partial class JumpTable + { + /// + /// Pushes the number of stack items in the evaluation stack onto the stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 0, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Depth(ExecutionEngine engine, Instruction instruction) + { + engine.Push(engine.CurrentContext!.EvaluationStack.Count); + } + + /// + /// Removes the top item from the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 1, Push 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Drop(ExecutionEngine engine, Instruction instruction) + { + engine.Pop(); + } + + /// + /// + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Nip(ExecutionEngine engine, Instruction instruction) + { + engine.CurrentContext!.EvaluationStack.Remove(1); + } + + /// + /// Removes the nth item from the top of the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 1, Push 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void XDrop(ExecutionEngine engine, Instruction instruction) + { + var n = (int)engine.Pop().GetInteger(); + if (n < 0) + throw new InvalidOperationException($"The negative value {n} is invalid for OpCode.{instruction.OpCode}."); + engine.CurrentContext!.EvaluationStack.Remove(n); + } + + /// + /// Clears all items from the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Clear(ExecutionEngine engine, Instruction instruction) + { + engine.CurrentContext!.EvaluationStack.Clear(); + } + + /// + /// Duplicates the item on the top of the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 0, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Dup(ExecutionEngine engine, Instruction instruction) + { + engine.Push(engine.Peek()); + } + + /// + /// Copies the second item from the top of the evaluation stack and pushes the copy onto the stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 0, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Over(ExecutionEngine engine, Instruction instruction) + { + engine.Push(engine.Peek(1)); + } + + /// + /// Copies the nth item from the top of the evaluation stack and pushes the copy onto the stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 1, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Pick(ExecutionEngine engine, Instruction instruction) + { + var n = (int)engine.Pop().GetInteger(); + if (n < 0) + throw new InvalidOperationException($"The negative value {n} is invalid for OpCode.{instruction.OpCode}."); + engine.Push(engine.Peek(n)); + } + + /// + /// Copies the top item on the evaluation stack and inserts the copy between the first and second items. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Tuck(ExecutionEngine engine, Instruction instruction) + { + engine.CurrentContext!.EvaluationStack.Insert(2, engine.Peek()); + } + + /// + /// Swaps the top two items on the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 0, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Swap(ExecutionEngine engine, Instruction instruction) + { + var x = engine.CurrentContext!.EvaluationStack.Remove(1); + engine.Push(x); + } + + /// + /// Left rotates the top three items on the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 0, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Rot(ExecutionEngine engine, Instruction instruction) + { + var x = engine.CurrentContext!.EvaluationStack.Remove(2); + engine.Push(x); + } + + /// + /// The item n back in the stack is moved to the top. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 1, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Roll(ExecutionEngine engine, Instruction instruction) + { + var n = (int)engine.Pop().GetInteger(); + if (n < 0) + throw new InvalidOperationException($"The negative value {n} is invalid for OpCode.{instruction.OpCode}."); + if (n == 0) return; + var x = engine.CurrentContext!.EvaluationStack.Remove(n); + engine.Push(x); + } + + /// + /// Reverses the order of the top 3 items on the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Reverse3(ExecutionEngine engine, Instruction instruction) + { + engine.CurrentContext!.EvaluationStack.Reverse(3); + } + + /// + /// Reverses the order of the top 4 items on the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Reverse4(ExecutionEngine engine, Instruction instruction) + { + engine.CurrentContext!.EvaluationStack.Reverse(4); + } + + /// + /// Reverses the order of the top n items on the evaluation stack. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 1, Push 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void ReverseN(ExecutionEngine engine, Instruction instruction) + { + var n = (int)engine.Pop().GetInteger(); + engine.CurrentContext!.EvaluationStack.Reverse(n); + } + } +} diff --git a/src/Neo.VM/JumpTable/JumpTable.Types.cs b/src/Neo.VM/JumpTable/JumpTable.Types.cs new file mode 100644 index 0000000000..717d759979 --- /dev/null +++ b/src/Neo.VM/JumpTable/JumpTable.Types.cs @@ -0,0 +1,98 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// JumpTable.Types.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.VM.Types; +using System; +using System.Runtime.CompilerServices; + +namespace Neo.VM +{ + /// + /// Partial class for type operations in the execution engine within a jump table. + /// + public partial class JumpTable + { + /// + /// Determines whether the item on top of the evaluation stack is null. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 1, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void IsNull(ExecutionEngine engine, Instruction instruction) + { + var x = engine.Pop(); + engine.Push(x.IsNull); + } + + /// + /// Determines whether the item on top of the evaluation stack has a specified type. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 1, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void IsType(ExecutionEngine engine, Instruction instruction) + { + var x = engine.Pop(); + var type = (StackItemType)instruction.TokenU8; + if (type == StackItemType.Any || !Enum.IsDefined(typeof(StackItemType), type)) + throw new InvalidOperationException($"Invalid type: {type}"); + engine.Push(x.Type == type); + } + + /// + /// Converts the item on top of the evaluation stack to a specified type. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 1, Push 1 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void Convert(ExecutionEngine engine, Instruction instruction) + { + var x = engine.Pop(); + engine.Push(x.ConvertTo((StackItemType)instruction.TokenU8)); + } + + /// + /// Aborts execution with a specified message. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 1, Push 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void AbortMsg(ExecutionEngine engine, Instruction instruction) + { + var msg = engine.Pop().GetString(); + throw new Exception($"{OpCode.ABORTMSG} is executed. Reason: {msg}"); + } + + /// + /// Asserts a condition with a specified message, throwing an exception if the condition is false. + /// + /// + /// The execution engine. + /// The instruction being executed. + /// Pop 2, Push 0 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual void AssertMsg(ExecutionEngine engine, Instruction instruction) + { + var msg = engine.Pop().GetString(); + var x = engine.Pop().GetBoolean(); + if (!x) + throw new Exception($"{OpCode.ASSERTMSG} is executed with false result. Reason: {msg}"); + } + } +} diff --git a/src/Neo.VM/JumpTable/JumpTable.cs b/src/Neo.VM/JumpTable/JumpTable.cs new file mode 100644 index 0000000000..0c387e7c9b --- /dev/null +++ b/src/Neo.VM/JumpTable/JumpTable.cs @@ -0,0 +1,71 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// JumpTable.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Runtime.CompilerServices; + +namespace Neo.VM +{ + public partial class JumpTable + { + /// + /// Default JumpTable + /// + public static readonly JumpTable Default = new(); + + public delegate void DelAction(ExecutionEngine engine, Instruction instruction); + protected readonly DelAction[] Table = new DelAction[byte.MaxValue]; + + public DelAction this[OpCode opCode] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Table[(byte)opCode]; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set { Table[(byte)opCode] = value; } + } + + /// + /// Jump table constructor + /// + /// Throw an exception if the opcode was already set + public JumpTable() + { + // Fill defined + + foreach (var mi in GetType().GetMethods()) + { + if (Enum.TryParse(mi.Name, true, out var opCode)) + { + if (Table[(byte)opCode] is not null) + { + throw new InvalidOperationException($"Opcode {opCode} is already defined."); + } + + Table[(byte)opCode] = (DelAction)mi.CreateDelegate(typeof(DelAction), this); + } + } + + // Fill with undefined + + for (var x = 0; x < Table.Length; x++) + { + if (Table[x] is not null) continue; + + Table[x] = InvalidOpcode; + } + } + + public virtual void InvalidOpcode(ExecutionEngine engine, Instruction instruction) + { + throw new InvalidOperationException($"Opcode {instruction.OpCode} is undefined."); + } + } +} diff --git a/src/Neo.VM/Neo.VM.csproj b/src/Neo.VM/Neo.VM.csproj new file mode 100644 index 0000000000..eb137b623a --- /dev/null +++ b/src/Neo.VM/Neo.VM.csproj @@ -0,0 +1,16 @@ + + + + netstandard2.1;net8.0 + true + enable + Neo.VM + ../../bin/$(PackageId) + + + + + + + + diff --git a/src/Neo.VM/OpCode.cs b/src/Neo.VM/OpCode.cs new file mode 100644 index 0000000000..dd5f1574ea --- /dev/null +++ b/src/Neo.VM/OpCode.cs @@ -0,0 +1,906 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// OpCode.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.VM.Types; + +namespace Neo.VM +{ + /// + /// Represents the opcode of an . + /// + public enum OpCode : byte + { + #region Constants + + /// + /// Pushes a 1-byte signed integer onto the stack. + /// + [OperandSize(Size = 1)] + PUSHINT8 = 0x00, + /// + /// Pushes a 2-bytes signed integer onto the stack. + /// + [OperandSize(Size = 2)] + PUSHINT16 = 0x01, + /// + /// Pushes a 4-bytes signed integer onto the stack. + /// + [OperandSize(Size = 4)] + PUSHINT32 = 0x02, + /// + /// Pushes a 8-bytes signed integer onto the stack. + /// + [OperandSize(Size = 8)] + PUSHINT64 = 0x03, + /// + /// Pushes a 16-bytes signed integer onto the stack. + /// + [OperandSize(Size = 16)] + PUSHINT128 = 0x04, + /// + /// Pushes a 32-bytes signed integer onto the stack. + /// + [OperandSize(Size = 32)] + PUSHINT256 = 0x05, + /// + /// Pushes the boolean value onto the stack. + /// + PUSHT = 0x08, + /// + /// Pushes the boolean value onto the stack. + /// + PUSHF = 0x09, + /// + /// Converts the 4-bytes offset to an , and pushes it onto the stack. + /// + [OperandSize(Size = 4)] + PUSHA = 0x0A, + /// + /// The item is pushed onto the stack. + /// + PUSHNULL = 0x0B, + /// + /// The next byte contains the number of bytes to be pushed onto the stack. + /// + [OperandSize(SizePrefix = 1)] + PUSHDATA1 = 0x0C, + /// + /// The next two bytes contain the number of bytes to be pushed onto the stack. + /// + [OperandSize(SizePrefix = 2)] + PUSHDATA2 = 0x0D, + /// + /// The next four bytes contain the number of bytes to be pushed onto the stack. + /// + [OperandSize(SizePrefix = 4)] + PUSHDATA4 = 0x0E, + /// + /// The number -1 is pushed onto the stack. + /// + PUSHM1 = 0x0F, + /// + /// The number 0 is pushed onto the stack. + /// + PUSH0 = 0x10, + /// + /// The number 1 is pushed onto the stack. + /// + PUSH1 = 0x11, + /// + /// The number 2 is pushed onto the stack. + /// + PUSH2 = 0x12, + /// + /// The number 3 is pushed onto the stack. + /// + PUSH3 = 0x13, + /// + /// The number 4 is pushed onto the stack. + /// + PUSH4 = 0x14, + /// + /// The number 5 is pushed onto the stack. + /// + PUSH5 = 0x15, + /// + /// The number 6 is pushed onto the stack. + /// + PUSH6 = 0x16, + /// + /// The number 7 is pushed onto the stack. + /// + PUSH7 = 0x17, + /// + /// The number 8 is pushed onto the stack. + /// + PUSH8 = 0x18, + /// + /// The number 9 is pushed onto the stack. + /// + PUSH9 = 0x19, + /// + /// The number 10 is pushed onto the stack. + /// + PUSH10 = 0x1A, + /// + /// The number 11 is pushed onto the stack. + /// + PUSH11 = 0x1B, + /// + /// The number 12 is pushed onto the stack. + /// + PUSH12 = 0x1C, + /// + /// The number 13 is pushed onto the stack. + /// + PUSH13 = 0x1D, + /// + /// The number 14 is pushed onto the stack. + /// + PUSH14 = 0x1E, + /// + /// The number 15 is pushed onto the stack. + /// + PUSH15 = 0x1F, + /// + /// The number 16 is pushed onto the stack. + /// + PUSH16 = 0x20, + + #endregion + + #region Flow control + + /// + /// The operation does nothing. It is intended to fill in space if opcodes are patched. + /// + NOP = 0x21, + /// + /// Unconditionally transfers control to a target instruction. The target instruction is represented as a 1-byte signed offset from the beginning of the current instruction. + /// + [OperandSize(Size = 1)] + JMP = 0x22, + /// + /// Unconditionally transfers control to a target instruction. The target instruction is represented as a 4-bytes signed offset from the beginning of the current instruction. + /// + [OperandSize(Size = 4)] + JMP_L = 0x23, + /// + /// Transfers control to a target instruction if the value is , not , or non-zero. The target instruction is represented as a 1-byte signed offset from the beginning of the current instruction. + /// + [OperandSize(Size = 1)] + JMPIF = 0x24, + /// + /// Transfers control to a target instruction if the value is , not , or non-zero. The target instruction is represented as a 4-bytes signed offset from the beginning of the current instruction. + /// + [OperandSize(Size = 4)] + JMPIF_L = 0x25, + /// + /// Transfers control to a target instruction if the value is , a reference, or zero. The target instruction is represented as a 1-byte signed offset from the beginning of the current instruction. + /// + [OperandSize(Size = 1)] + JMPIFNOT = 0x26, + /// + /// Transfers control to a target instruction if the value is , a reference, or zero. The target instruction is represented as a 4-bytes signed offset from the beginning of the current instruction. + /// + [OperandSize(Size = 4)] + JMPIFNOT_L = 0x27, + /// + /// Transfers control to a target instruction if two values are equal. The target instruction is represented as a 1-byte signed offset from the beginning of the current instruction. + /// + [OperandSize(Size = 1)] + JMPEQ = 0x28, + /// + /// Transfers control to a target instruction if two values are equal. The target instruction is represented as a 4-bytes signed offset from the beginning of the current instruction. + /// + [OperandSize(Size = 4)] + JMPEQ_L = 0x29, + /// + /// Transfers control to a target instruction when two values are not equal. The target instruction is represented as a 1-byte signed offset from the beginning of the current instruction. + /// + [OperandSize(Size = 1)] + JMPNE = 0x2A, + /// + /// Transfers control to a target instruction when two values are not equal. The target instruction is represented as a 4-bytes signed offset from the beginning of the current instruction. + /// + [OperandSize(Size = 4)] + JMPNE_L = 0x2B, + /// + /// Transfers control to a target instruction if the first value is greater than the second value. The target instruction is represented as a 1-byte signed offset from the beginning of the current instruction. + /// + [OperandSize(Size = 1)] + JMPGT = 0x2C, + /// + /// Transfers control to a target instruction if the first value is greater than the second value. The target instruction is represented as a 4-bytes signed offset from the beginning of the current instruction. + /// + [OperandSize(Size = 4)] + JMPGT_L = 0x2D, + /// + /// Transfers control to a target instruction if the first value is greater than or equal to the second value. The target instruction is represented as a 1-byte signed offset from the beginning of the current instruction. + /// + [OperandSize(Size = 1)] + JMPGE = 0x2E, + /// + /// Transfers control to a target instruction if the first value is greater than or equal to the second value. The target instruction is represented as a 4-bytes signed offset from the beginning of the current instruction. + /// + [OperandSize(Size = 4)] + JMPGE_L = 0x2F, + /// + /// Transfers control to a target instruction if the first value is less than the second value. The target instruction is represented as a 1-byte signed offset from the beginning of the current instruction. + /// + [OperandSize(Size = 1)] + JMPLT = 0x30, + /// + /// Transfers control to a target instruction if the first value is less than the second value. The target instruction is represented as a 4-bytes signed offset from the beginning of the current instruction. + /// + [OperandSize(Size = 4)] + JMPLT_L = 0x31, + /// + /// Transfers control to a target instruction if the first value is less than or equal to the second value. The target instruction is represented as a 1-byte signed offset from the beginning of the current instruction. + /// + [OperandSize(Size = 1)] + JMPLE = 0x32, + /// + /// Transfers control to a target instruction if the first value is less than or equal to the second value. The target instruction is represented as a 4-bytes signed offset from the beginning of the current instruction. + /// + [OperandSize(Size = 4)] + JMPLE_L = 0x33, + /// + /// Calls the function at the target address which is represented as a 1-byte signed offset from the beginning of the current instruction. + /// + [OperandSize(Size = 1)] + CALL = 0x34, + /// + /// Calls the function at the target address which is represented as a 4-bytes signed offset from the beginning of the current instruction. + /// + [OperandSize(Size = 4)] + CALL_L = 0x35, + /// + /// Pop the address of a function from the stack, and call the function. + /// + CALLA = 0x36, + /// + /// Calls the function which is described by the token. + /// + [OperandSize(Size = 2)] + CALLT = 0x37, + /// + /// It turns the vm state to FAULT immediately, and cannot be caught. + /// + ABORT = 0x38, + /// + /// Pop the top value of the stack. If it's false, exit vm execution and set vm state to FAULT. + /// + ASSERT = 0x39, + /// + /// Pop the top value of the stack, and throw it. + /// + THROW = 0x3A, + /// + /// TRY CatchOffset(sbyte) FinallyOffset(sbyte). If there's no catch body, set CatchOffset 0. If there's no finally body, set FinallyOffset 0. + /// + [OperandSize(Size = 2)] + TRY = 0x3B, + /// + /// TRY_L CatchOffset(int) FinallyOffset(int). If there's no catch body, set CatchOffset 0. If there's no finally body, set FinallyOffset 0. + /// + [OperandSize(Size = 8)] + TRY_L = 0x3C, + /// + /// Ensures that the appropriate surrounding finally blocks are executed. And then unconditionally transfers control to the specific target instruction, represented as a 1-byte signed offset from the beginning of the current instruction. + /// + [OperandSize(Size = 1)] + ENDTRY = 0x3D, + /// + /// Ensures that the appropriate surrounding finally blocks are executed. And then unconditionally transfers control to the specific target instruction, represented as a 4-byte signed offset from the beginning of the current instruction. + /// + [OperandSize(Size = 4)] + ENDTRY_L = 0x3E, + /// + /// End finally, If no exception happen or be catched, vm will jump to the target instruction of ENDTRY/ENDTRY_L. Otherwise vm will rethrow the exception to upper layer. + /// + ENDFINALLY = 0x3F, + /// + /// Returns from the current method. + /// + RET = 0x40, + /// + /// Calls to an interop service. + /// + [OperandSize(Size = 4)] + SYSCALL = 0x41, + + #endregion + + #region Stack + + /// + /// Puts the number of stack items onto the stack. + /// + DEPTH = 0x43, + /// + /// Removes the top stack item. + /// + DROP = 0x45, + /// + /// Removes the second-to-top stack item. + /// + NIP = 0x46, + /// + /// The item n back in the main stack is removed. + /// + XDROP = 0x48, + /// + /// Clear the stack + /// + CLEAR = 0x49, + /// + /// Duplicates the top stack item. + /// + DUP = 0x4A, + /// + /// Copies the second-to-top stack item to the top. + /// + OVER = 0x4B, + /// + /// The item n back in the stack is copied to the top. + /// + PICK = 0x4D, + /// + /// The item at the top of the stack is copied and inserted before the second-to-top item. + /// + TUCK = 0x4E, + /// + /// The top two items on the stack are swapped. + /// + SWAP = 0x50, + /// + /// The top three items on the stack are rotated to the left. + /// + ROT = 0x51, + /// + /// The item n back in the stack is moved to the top. + /// + ROLL = 0x52, + /// + /// Reverse the order of the top 3 items on the stack. + /// + REVERSE3 = 0x53, + /// + /// Reverse the order of the top 4 items on the stack. + /// + REVERSE4 = 0x54, + /// + /// Pop the number N on the stack, and reverse the order of the top N items on the stack. + /// + REVERSEN = 0x55, + + #endregion + + #region Slot + + /// + /// Initialize the static field list for the current execution context. + /// + [OperandSize(Size = 1)] + INITSSLOT = 0x56, + /// + /// Initialize the argument slot and the local variable list for the current execution context. + /// + [OperandSize(Size = 2)] + INITSLOT = 0x57, + /// + /// Loads the static field at index 0 onto the evaluation stack. + /// + LDSFLD0 = 0x58, + /// + /// Loads the static field at index 1 onto the evaluation stack. + /// + LDSFLD1 = 0x59, + /// + /// Loads the static field at index 2 onto the evaluation stack. + /// + LDSFLD2 = 0x5A, + /// + /// Loads the static field at index 3 onto the evaluation stack. + /// + LDSFLD3 = 0x5B, + /// + /// Loads the static field at index 4 onto the evaluation stack. + /// + LDSFLD4 = 0x5C, + /// + /// Loads the static field at index 5 onto the evaluation stack. + /// + LDSFLD5 = 0x5D, + /// + /// Loads the static field at index 6 onto the evaluation stack. + /// + LDSFLD6 = 0x5E, + /// + /// Loads the static field at a specified index onto the evaluation stack. The index is represented as a 1-byte unsigned integer. + /// + [OperandSize(Size = 1)] + LDSFLD = 0x5F, + /// + /// Stores the value on top of the evaluation stack in the static field list at index 0. + /// + STSFLD0 = 0x60, + /// + /// Stores the value on top of the evaluation stack in the static field list at index 1. + /// + STSFLD1 = 0x61, + /// + /// Stores the value on top of the evaluation stack in the static field list at index 2. + /// + STSFLD2 = 0x62, + /// + /// Stores the value on top of the evaluation stack in the static field list at index 3. + /// + STSFLD3 = 0x63, + /// + /// Stores the value on top of the evaluation stack in the static field list at index 4. + /// + STSFLD4 = 0x64, + /// + /// Stores the value on top of the evaluation stack in the static field list at index 5. + /// + STSFLD5 = 0x65, + /// + /// Stores the value on top of the evaluation stack in the static field list at index 6. + /// + STSFLD6 = 0x66, + /// + /// Stores the value on top of the evaluation stack in the static field list at a specified index. The index is represented as a 1-byte unsigned integer. + /// + [OperandSize(Size = 1)] + STSFLD = 0x67, + /// + /// Loads the local variable at index 0 onto the evaluation stack. + /// + LDLOC0 = 0x68, + /// + /// Loads the local variable at index 1 onto the evaluation stack. + /// + LDLOC1 = 0x69, + /// + /// Loads the local variable at index 2 onto the evaluation stack. + /// + LDLOC2 = 0x6A, + /// + /// Loads the local variable at index 3 onto the evaluation stack. + /// + LDLOC3 = 0x6B, + /// + /// Loads the local variable at index 4 onto the evaluation stack. + /// + LDLOC4 = 0x6C, + /// + /// Loads the local variable at index 5 onto the evaluation stack. + /// + LDLOC5 = 0x6D, + /// + /// Loads the local variable at index 6 onto the evaluation stack. + /// + LDLOC6 = 0x6E, + /// + /// Loads the local variable at a specified index onto the evaluation stack. The index is represented as a 1-byte unsigned integer. + /// + [OperandSize(Size = 1)] + LDLOC = 0x6F, + /// + /// Stores the value on top of the evaluation stack in the local variable list at index 0. + /// + STLOC0 = 0x70, + /// + /// Stores the value on top of the evaluation stack in the local variable list at index 1. + /// + STLOC1 = 0x71, + /// + /// Stores the value on top of the evaluation stack in the local variable list at index 2. + /// + STLOC2 = 0x72, + /// + /// Stores the value on top of the evaluation stack in the local variable list at index 3. + /// + STLOC3 = 0x73, + /// + /// Stores the value on top of the evaluation stack in the local variable list at index 4. + /// + STLOC4 = 0x74, + /// + /// Stores the value on top of the evaluation stack in the local variable list at index 5. + /// + STLOC5 = 0x75, + /// + /// Stores the value on top of the evaluation stack in the local variable list at index 6. + /// + STLOC6 = 0x76, + /// + /// Stores the value on top of the evaluation stack in the local variable list at a specified index. The index is represented as a 1-byte unsigned integer. + /// + [OperandSize(Size = 1)] + STLOC = 0x77, + /// + /// Loads the argument at index 0 onto the evaluation stack. + /// + LDARG0 = 0x78, + /// + /// Loads the argument at index 1 onto the evaluation stack. + /// + LDARG1 = 0x79, + /// + /// Loads the argument at index 2 onto the evaluation stack. + /// + LDARG2 = 0x7A, + /// + /// Loads the argument at index 3 onto the evaluation stack. + /// + LDARG3 = 0x7B, + /// + /// Loads the argument at index 4 onto the evaluation stack. + /// + LDARG4 = 0x7C, + /// + /// Loads the argument at index 5 onto the evaluation stack. + /// + LDARG5 = 0x7D, + /// + /// Loads the argument at index 6 onto the evaluation stack. + /// + LDARG6 = 0x7E, + /// + /// Loads the argument at a specified index onto the evaluation stack. The index is represented as a 1-byte unsigned integer. + /// + [OperandSize(Size = 1)] + LDARG = 0x7F, + /// + /// Stores the value on top of the evaluation stack in the argument slot at index 0. + /// + STARG0 = 0x80, + /// + /// Stores the value on top of the evaluation stack in the argument slot at index 1. + /// + STARG1 = 0x81, + /// + /// Stores the value on top of the evaluation stack in the argument slot at index 2. + /// + STARG2 = 0x82, + /// + /// Stores the value on top of the evaluation stack in the argument slot at index 3. + /// + STARG3 = 0x83, + /// + /// Stores the value on top of the evaluation stack in the argument slot at index 4. + /// + STARG4 = 0x84, + /// + /// Stores the value on top of the evaluation stack in the argument slot at index 5. + /// + STARG5 = 0x85, + /// + /// Stores the value on top of the evaluation stack in the argument slot at index 6. + /// + STARG6 = 0x86, + /// + /// Stores the value on top of the evaluation stack in the argument slot at a specified index. The index is represented as a 1-byte unsigned integer. + /// + [OperandSize(Size = 1)] + STARG = 0x87, + + #endregion + + #region Splice + + /// + /// Creates a new and pushes it onto the stack. + /// + NEWBUFFER = 0x88, + /// + /// Copies a range of bytes from one to another. + /// + MEMCPY = 0x89, + /// + /// Concatenates two strings. + /// + CAT = 0x8B, + /// + /// Returns a section of a string. + /// + SUBSTR = 0x8C, + /// + /// Keeps only characters left of the specified point in a string. + /// + LEFT = 0x8D, + /// + /// Keeps only characters right of the specified point in a string. + /// + RIGHT = 0x8E, + + #endregion + + #region Bitwise logic + + /// + /// Flips all of the bits in the input. + /// + INVERT = 0x90, + /// + /// Boolean and between each bit in the inputs. + /// + AND = 0x91, + /// + /// Boolean or between each bit in the inputs. + /// + OR = 0x92, + /// + /// Boolean exclusive or between each bit in the inputs. + /// + XOR = 0x93, + /// + /// Returns 1 if the inputs are exactly equal, 0 otherwise. + /// + EQUAL = 0x97, + /// + /// Returns 1 if the inputs are not equal, 0 otherwise. + /// + NOTEQUAL = 0x98, + + #endregion + + #region Arithmetic + + /// + /// Puts the sign of top stack item on top of the main stack. If value is negative, put -1; if positive, put 1; if value is zero, put 0. + /// + SIGN = 0x99, + /// + /// The input is made positive. + /// + ABS = 0x9A, + /// + /// The sign of the input is flipped. + /// + NEGATE = 0x9B, + /// + /// 1 is added to the input. + /// + INC = 0x9C, + /// + /// 1 is subtracted from the input. + /// + DEC = 0x9D, + /// + /// a is added to b. + /// + ADD = 0x9E, + /// + /// b is subtracted from a. + /// + SUB = 0x9F, + /// + /// a is multiplied by b. + /// + MUL = 0xA0, + /// + /// a is divided by b. + /// + DIV = 0xA1, + /// + /// Returns the remainder after dividing a by b. + /// + MOD = 0xA2, + /// + /// The result of raising value to the exponent power. + /// + POW = 0xA3, + /// + /// Returns the square root of a specified number. + /// + SQRT = 0xA4, + /// + /// Performs modulus division on a number multiplied by another number. + /// + MODMUL = 0xA5, + /// + /// Performs modulus division on a number raised to the power of another number. If the exponent is -1, it will have the calculation of the modular inverse. + /// + MODPOW = 0xA6, + /// + /// Shifts a left b bits, preserving sign. + /// + SHL = 0xA8, + /// + /// Shifts a right b bits, preserving sign. + /// + SHR = 0xA9, + /// + /// If the input is 0 or 1, it is flipped. Otherwise the output will be 0. + /// + NOT = 0xAA, + /// + /// If both a and b are not 0, the output is 1. Otherwise 0. + /// + BOOLAND = 0xAB, + /// + /// If a or b is not 0, the output is 1. Otherwise 0. + /// + BOOLOR = 0xAC, + /// + /// Returns 0 if the input is 0. 1 otherwise. + /// + NZ = 0xB1, + /// + /// Returns 1 if the numbers are equal, 0 otherwise. + /// + NUMEQUAL = 0xB3, + /// + /// Returns 1 if the numbers are not equal, 0 otherwise. + /// + NUMNOTEQUAL = 0xB4, + /// + /// Returns 1 if a is less than b, 0 otherwise. + /// + LT = 0xB5, + /// + /// Returns 1 if a is less than or equal to b, 0 otherwise. + /// + LE = 0xB6, + /// + /// Returns 1 if a is greater than b, 0 otherwise. + /// + GT = 0xB7, + /// + /// Returns 1 if a is greater than or equal to b, 0 otherwise. + /// + GE = 0xB8, + /// + /// Returns the smaller of a and b. + /// + MIN = 0xB9, + /// + /// Returns the larger of a and b. + /// + MAX = 0xBA, + /// + /// Returns 1 if x is within the specified range (left-inclusive), 0 otherwise. + /// + WITHIN = 0xBB, + + #endregion + + #region Compound-type + + /// + /// A value n is taken from top of main stack. The next n*2 items on main stack are removed, put inside n-sized map and this map is put on top of the main stack. + /// + PACKMAP = 0xBE, + /// + /// A value n is taken from top of main stack. The next n items on main stack are removed, put inside n-sized struct and this struct is put on top of the main stack. + /// + PACKSTRUCT = 0xBF, + /// + /// A value n is taken from top of main stack. The next n items on main stack are removed, put inside n-sized array and this array is put on top of the main stack. + /// + PACK = 0xC0, + /// + /// A collection is removed from top of the main stack. Its elements are put on top of the main stack (in reverse order) and the collection size is also put on main stack. + /// + UNPACK = 0xC1, + /// + /// An empty array (with size 0) is put on top of the main stack. + /// + NEWARRAY0 = 0xC2, + /// + /// A value n is taken from top of main stack. A null-filled array with size n is put on top of the main stack. + /// + NEWARRAY = 0xC3, + /// + /// A value n is taken from top of main stack. An array of type T with size n is put on top of the main stack. + /// + [OperandSize(Size = 1)] + NEWARRAY_T = 0xC4, + /// + /// An empty struct (with size 0) is put on top of the main stack. + /// + NEWSTRUCT0 = 0xC5, + /// + /// A value n is taken from top of main stack. A zero-filled struct with size n is put on top of the main stack. + /// + NEWSTRUCT = 0xC6, + /// + /// A Map is created and put on top of the main stack. + /// + NEWMAP = 0xC8, + /// + /// An array is removed from top of the main stack. Its size is put on top of the main stack. + /// + SIZE = 0xCA, + /// + /// An input index n (or key) and an array (or map) are removed from the top of the main stack. Puts True on top of main stack if array[n] (or map[n]) exist, and False otherwise. + /// + HASKEY = 0xCB, + /// + /// A map is taken from top of the main stack. The keys of this map are put on top of the main stack. + /// + KEYS = 0xCC, + /// + /// A map is taken from top of the main stack. The values of this map are put on top of the main stack. + /// + VALUES = 0xCD, + /// + /// An input index n (or key) and an array (or map) are taken from main stack. Element array[n] (or map[n]) is put on top of the main stack. + /// + PICKITEM = 0xCE, + /// + /// The item on top of main stack is removed and appended to the second item on top of the main stack. + /// + APPEND = 0xCF, + /// + /// A value v, index n (or key) and an array (or map) are taken from main stack. Attribution array[n]=v (or map[n]=v) is performed. + /// + SETITEM = 0xD0, + /// + /// An array is removed from the top of the main stack and its elements are reversed. + /// + REVERSEITEMS = 0xD1, + /// + /// An input index n (or key) and an array (or map) are removed from the top of the main stack. Element array[n] (or map[n]) is removed. + /// + REMOVE = 0xD2, + /// + /// Remove all the items from the compound-type. + /// + CLEARITEMS = 0xD3, + /// + /// Remove the last element from an array, and push it onto the stack. + /// + POPITEM = 0xD4, + + #endregion + + #region Types + + /// + /// Returns if the input is ; + /// otherwise. + /// + ISNULL = 0xD8, + /// + /// Returns if the top item of the stack is of the specified type; + /// otherwise. + /// + [OperandSize(Size = 1)] + ISTYPE = 0xD9, + /// + /// Converts the top item of the stack to the specified type. + /// + [OperandSize(Size = 1)] + CONVERT = 0xDB, + + #endregion + + #region Extensions + + /// + /// Pops the top stack item. Then, turns the vm state to FAULT immediately, and cannot be caught. The top stack + /// value is used as reason. + /// + ABORTMSG = 0xE0, + /// + /// Pops the top two stack items. If the second-to-top stack value is false, exits the vm execution and sets the + /// vm state to FAULT. In this case, the top stack value is used as reason for the exit. Otherwise, it is ignored. + /// + ASSERTMSG = 0xE1 + + #endregion + } +} diff --git a/src/Neo.VM/OperandSizeAttribute.cs b/src/Neo.VM/OperandSizeAttribute.cs new file mode 100644 index 0000000000..592c0a716a --- /dev/null +++ b/src/Neo.VM/OperandSizeAttribute.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// OperandSizeAttribute.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; + +namespace Neo.VM +{ + /// + /// Indicates the operand length of an . + /// + [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] + public class OperandSizeAttribute : Attribute + { + /// + /// When it is greater than 0, indicates the size of the operand. + /// + public int Size { get; set; } + + /// + /// When it is greater than 0, indicates the size prefix of the operand. + /// + public int SizePrefix { get; set; } + } +} diff --git a/src/Neo.VM/Properties/AssemblyInfo.cs b/src/Neo.VM/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..71329c03a1 --- /dev/null +++ b/src/Neo.VM/Properties/AssemblyInfo.cs @@ -0,0 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// AssemblyInfo.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Neo.VM.Tests")] diff --git a/src/Neo.VM/ReferenceCounter.cs b/src/Neo.VM/ReferenceCounter.cs new file mode 100644 index 0000000000..be7844dac8 --- /dev/null +++ b/src/Neo.VM/ReferenceCounter.cs @@ -0,0 +1,155 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ReferenceCounter.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.VM.StronglyConnectedComponents; +using Neo.VM.Types; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Neo.VM +{ + /// + /// Used for reference counting of objects in the VM. + /// + public sealed class ReferenceCounter + { + private const bool TrackAllItems = false; + + private readonly HashSet tracked_items = new(ReferenceEqualityComparer.Instance); + private readonly HashSet zero_referred = new(ReferenceEqualityComparer.Instance); + private LinkedList>? cached_components; + private int references_count = 0; + + /// + /// Indicates the number of this counter. + /// + public int Count => references_count; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool NeedTrack(StackItem item) + { +#pragma warning disable CS0162 + if (TrackAllItems) return true; +#pragma warning restore CS0162 + if (item is CompoundType or Buffer) return true; + return false; + } + + internal void AddReference(StackItem item, CompoundType parent) + { + references_count++; + if (!NeedTrack(item)) return; + cached_components = null; + tracked_items.Add(item); + item.ObjectReferences ??= new(ReferenceEqualityComparer.Instance); + if (!item.ObjectReferences.TryGetValue(parent, out var pEntry)) + { + pEntry = new(parent); + item.ObjectReferences.Add(parent, pEntry); + } + pEntry.References++; + } + + internal void AddStackReference(StackItem item, int count = 1) + { + references_count += count; + if (!NeedTrack(item)) return; + if (tracked_items.Add(item)) + cached_components?.AddLast(new HashSet(ReferenceEqualityComparer.Instance) { item }); + item.StackReferences += count; + zero_referred.Remove(item); + } + + internal void AddZeroReferred(StackItem item) + { + zero_referred.Add(item); + if (!NeedTrack(item)) return; + cached_components?.AddLast(new HashSet(ReferenceEqualityComparer.Instance) { item }); + tracked_items.Add(item); + } + + internal int CheckZeroReferred() + { + if (zero_referred.Count > 0) + { + zero_referred.Clear(); + if (cached_components is null) + { + //Tarjan tarjan = new(tracked_items.Where(p => p.StackReferences == 0)); + Tarjan tarjan = new(tracked_items); + cached_components = tarjan.Invoke(); + } + foreach (StackItem item in tracked_items) + item.Reset(); + for (var node = cached_components.First; node != null;) + { + var component = node.Value; + bool on_stack = false; + foreach (StackItem item in component) + { + if (item.StackReferences > 0 || item.ObjectReferences?.Values.Any(p => p.References > 0 && p.Item.OnStack) == true) + { + on_stack = true; + break; + } + } + if (on_stack) + { + foreach (StackItem item in component) + item.OnStack = true; + node = node.Next; + } + else + { + foreach (StackItem item in component) + { + tracked_items.Remove(item); + if (item is CompoundType compound) + { + references_count -= compound.SubItemsCount; + foreach (StackItem subitem in compound.SubItems) + { + if (component.Contains(subitem)) continue; + if (!NeedTrack(subitem)) continue; + subitem.ObjectReferences!.Remove(compound); + } + } + item.Cleanup(); + } + var nodeToRemove = node; + node = node.Next; + cached_components.Remove(nodeToRemove); + } + } + } + return references_count; + } + + internal void RemoveReference(StackItem item, CompoundType parent) + { + references_count--; + if (!NeedTrack(item)) return; + cached_components = null; + item.ObjectReferences![parent].References--; + if (item.StackReferences == 0) + zero_referred.Add(item); + } + + internal void RemoveStackReference(StackItem item) + { + references_count--; + if (!NeedTrack(item)) return; + if (--item.StackReferences == 0) + zero_referred.Add(item); + } + } +} diff --git a/src/Neo.VM/ReferenceEqualityComparer.cs b/src/Neo.VM/ReferenceEqualityComparer.cs new file mode 100644 index 0000000000..4e8eec9d2d --- /dev/null +++ b/src/Neo.VM/ReferenceEqualityComparer.cs @@ -0,0 +1,30 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ReferenceEqualityComparer.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Neo.VM +{ +#if !NET5_0_OR_GREATER + // https://github.dev/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/ReferenceEqualityComparer.cs + public sealed class ReferenceEqualityComparer : IEqualityComparer, System.Collections.IEqualityComparer + { + private ReferenceEqualityComparer() { } + + public static ReferenceEqualityComparer Instance { get; } = new ReferenceEqualityComparer(); + + public new bool Equals(object? x, object? y) => ReferenceEquals(x, y); + + public int GetHashCode(object? obj) => RuntimeHelpers.GetHashCode(obj!); + } +#endif +} diff --git a/src/Neo.VM/Script.cs b/src/Neo.VM/Script.cs new file mode 100644 index 0000000000..95307c94f4 --- /dev/null +++ b/src/Neo.VM/Script.cs @@ -0,0 +1,161 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Script.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.VM.Types; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Neo.VM +{ + /// + /// Represents the script executed in the VM. + /// + [DebuggerDisplay("Length={Length}")] + public class Script + { + private readonly ReadOnlyMemory _value; + private readonly bool strictMode; + private readonly Dictionary _instructions = new(); + + /// + /// The length of the script. + /// + public int Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return _value.Length; + } + } + + /// + /// Gets the at the specified index. + /// + /// The index to locate. + /// The at the specified index. + public OpCode this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return (OpCode)_value.Span[index]; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The bytecodes of the script. + public Script(ReadOnlyMemory script) : this(script, false) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The bytecodes of the script. + /// + /// Indicates whether strict mode is enabled. + /// In strict mode, the script will be checked, but the loading speed will be slower. + /// + /// In strict mode, the script was found to contain bad instructions. + public Script(ReadOnlyMemory script, bool strictMode) + { + _value = script; + if (strictMode) + { + for (int ip = 0; ip < script.Length; ip += GetInstruction(ip).Size) { } + foreach (var (ip, instruction) in _instructions) + { + switch (instruction.OpCode) + { + case OpCode.JMP: + case OpCode.JMPIF: + case OpCode.JMPIFNOT: + case OpCode.JMPEQ: + case OpCode.JMPNE: + case OpCode.JMPGT: + case OpCode.JMPGE: + case OpCode.JMPLT: + case OpCode.JMPLE: + case OpCode.CALL: + case OpCode.ENDTRY: + if (!_instructions.ContainsKey(checked(ip + instruction.TokenI8))) + throw new BadScriptException($"ip: {ip}, opcode: {instruction.OpCode}"); + break; + case OpCode.PUSHA: + case OpCode.JMP_L: + case OpCode.JMPIF_L: + case OpCode.JMPIFNOT_L: + case OpCode.JMPEQ_L: + case OpCode.JMPNE_L: + case OpCode.JMPGT_L: + case OpCode.JMPGE_L: + case OpCode.JMPLT_L: + case OpCode.JMPLE_L: + case OpCode.CALL_L: + case OpCode.ENDTRY_L: + if (!_instructions.ContainsKey(checked(ip + instruction.TokenI32))) + throw new BadScriptException($"ip: {ip}, opcode: {instruction.OpCode}"); + break; + case OpCode.TRY: + if (!_instructions.ContainsKey(checked(ip + instruction.TokenI8))) + throw new BadScriptException($"ip: {ip}, opcode: {instruction.OpCode}"); + if (!_instructions.ContainsKey(checked(ip + instruction.TokenI8_1))) + throw new BadScriptException($"ip: {ip}, opcode: {instruction.OpCode}"); + break; + case OpCode.TRY_L: + if (!_instructions.ContainsKey(checked(ip + instruction.TokenI32))) + throw new BadScriptException($"ip: {ip}, opcode: {instruction.OpCode}"); + if (!_instructions.ContainsKey(checked(ip + instruction.TokenI32_1))) + throw new BadScriptException($"ip: {ip}, opcode: {instruction.OpCode}"); + break; + case OpCode.NEWARRAY_T: + case OpCode.ISTYPE: + case OpCode.CONVERT: + StackItemType type = (StackItemType)instruction.TokenU8; + if (!Enum.IsDefined(typeof(StackItemType), type)) + throw new BadScriptException(); + if (instruction.OpCode != OpCode.NEWARRAY_T && type == StackItemType.Any) + throw new BadScriptException($"ip: {ip}, opcode: {instruction.OpCode}"); + break; + } + } + } + this.strictMode = strictMode; + } + + /// + /// Get the at the specified position. + /// + /// The position to get the . + /// The at the specified position. + /// In strict mode, the was not found at the specified position. + public Instruction GetInstruction(int ip) + { + if (ip >= Length) throw new ArgumentOutOfRangeException(nameof(ip)); + if (!_instructions.TryGetValue(ip, out Instruction? instruction)) + { + if (strictMode) throw new ArgumentException($"ip not found with strict mode", nameof(ip)); + instruction = new Instruction(_value, ip); + _instructions.Add(ip, instruction); + } + return instruction; + } + + public static implicit operator ReadOnlyMemory(Script script) => script._value; + public static implicit operator Script(ReadOnlyMemory script) => new(script); + public static implicit operator Script(byte[] script) => new(script); + } +} diff --git a/src/Neo.VM/ScriptBuilder.cs b/src/Neo.VM/ScriptBuilder.cs new file mode 100644 index 0000000000..3b2f83171a --- /dev/null +++ b/src/Neo.VM/ScriptBuilder.cs @@ -0,0 +1,200 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ScriptBuilder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.IO; +using System.Numerics; + +namespace Neo.VM +{ + /// + /// A helper class for building scripts. + /// + public class ScriptBuilder : IDisposable + { + private readonly MemoryStream ms = new(); + private readonly BinaryWriter writer; + + /// + /// The length of the script. + /// + public int Length => (int)ms.Position; + + /// + /// Initializes a new instance of the class. + /// + public ScriptBuilder() + { + writer = new BinaryWriter(ms); + } + + public void Dispose() + { + writer.Dispose(); + ms.Dispose(); + } + + /// + /// Emits an with the specified and operand. + /// + /// The to be emitted. + /// The operand to be emitted. + /// A reference to this instance after the emit operation has completed. + public ScriptBuilder Emit(OpCode opcode, ReadOnlySpan operand = default) + { + writer.Write((byte)opcode); + writer.Write(operand); + return this; + } + + /// + /// Emits a call with the specified offset. + /// + /// The offset to be called. + /// A reference to this instance after the emit operation has completed. + public ScriptBuilder EmitCall(int offset) + { + if (offset < sbyte.MinValue || offset > sbyte.MaxValue) + return Emit(OpCode.CALL_L, BitConverter.GetBytes(offset)); + else + return Emit(OpCode.CALL, new[] { (byte)offset }); + } + + /// + /// Emits a jump with the specified offset. + /// + /// The to be emitted. It must be a jump + /// The offset to jump. + /// A reference to this instance after the emit operation has completed. + public ScriptBuilder EmitJump(OpCode opcode, int offset) + { + if (opcode < OpCode.JMP || opcode > OpCode.JMPLE_L) + throw new ArgumentOutOfRangeException(nameof(opcode)); + if ((int)opcode % 2 == 0 && (offset < sbyte.MinValue || offset > sbyte.MaxValue)) + opcode += 1; + if ((int)opcode % 2 == 0) + return Emit(opcode, new[] { (byte)offset }); + else + return Emit(opcode, BitConverter.GetBytes(offset)); + } + + /// + /// Emits a push with the specified number. + /// + /// The number to be pushed. + /// A reference to this instance after the emit operation has completed. + public ScriptBuilder EmitPush(BigInteger value) + { + if (value >= -1 && value <= 16) return Emit(OpCode.PUSH0 + (byte)(int)value); + Span buffer = stackalloc byte[32]; + if (!value.TryWriteBytes(buffer, out int bytesWritten, isUnsigned: false, isBigEndian: false)) + throw new ArgumentOutOfRangeException(nameof(value)); + return bytesWritten switch + { + 1 => Emit(OpCode.PUSHINT8, PadRight(buffer, bytesWritten, 1, value.Sign < 0)), + 2 => Emit(OpCode.PUSHINT16, PadRight(buffer, bytesWritten, 2, value.Sign < 0)), + <= 4 => Emit(OpCode.PUSHINT32, PadRight(buffer, bytesWritten, 4, value.Sign < 0)), + <= 8 => Emit(OpCode.PUSHINT64, PadRight(buffer, bytesWritten, 8, value.Sign < 0)), + <= 16 => Emit(OpCode.PUSHINT128, PadRight(buffer, bytesWritten, 16, value.Sign < 0)), + _ => Emit(OpCode.PUSHINT256, PadRight(buffer, bytesWritten, 32, value.Sign < 0)), + }; + } + + /// + /// Emits a push with the specified boolean value. + /// + /// The value to be pushed. + /// A reference to this instance after the emit operation has completed. + public ScriptBuilder EmitPush(bool value) + { + return Emit(value ? OpCode.PUSHT : OpCode.PUSHF); + } + + /// + /// Emits a push with the specified data. + /// + /// The data to be pushed. + /// A reference to this instance after the emit operation has completed. + public ScriptBuilder EmitPush(ReadOnlySpan data) + { + if (data == null) + throw new ArgumentNullException(nameof(data)); + if (data.Length < 0x100) + { + Emit(OpCode.PUSHDATA1); + writer.Write((byte)data.Length); + writer.Write(data); + } + else if (data.Length < 0x10000) + { + Emit(OpCode.PUSHDATA2); + writer.Write((ushort)data.Length); + writer.Write(data); + } + else// if (data.Length < 0x100000000L) + { + Emit(OpCode.PUSHDATA4); + writer.Write(data.Length); + writer.Write(data); + } + return this; + } + + /// + /// Emits a push with the specified . + /// + /// The to be pushed. + /// A reference to this instance after the emit operation has completed. + public ScriptBuilder EmitPush(string data) + { + return EmitPush(Utility.StrictUTF8.GetBytes(data)); + } + + /// + /// Emits raw script. + /// + /// The raw script to be emitted. + /// A reference to this instance after the emit operation has completed. + public ScriptBuilder EmitRaw(ReadOnlySpan script = default) + { + writer.Write(script); + return this; + } + + /// + /// Emits an with . + /// + /// The operand of . + /// A reference to this instance after the emit operation has completed. + public ScriptBuilder EmitSysCall(uint api) + { + return Emit(OpCode.SYSCALL, BitConverter.GetBytes(api)); + } + + /// + /// Converts the value of this instance to a byte array. + /// + /// A byte array contains the script. + public byte[] ToArray() + { + writer.Flush(); + return ms.ToArray(); + } + + private static ReadOnlySpan PadRight(Span buffer, int dataLength, int padLength, bool negative) + { + byte pad = negative ? (byte)0xff : (byte)0; + for (int x = dataLength; x < padLength; x++) + buffer[x] = pad; + return buffer[..padLength]; + } + } +} diff --git a/src/Neo.VM/Slot.cs b/src/Neo.VM/Slot.cs new file mode 100644 index 0000000000..7812694ce6 --- /dev/null +++ b/src/Neo.VM/Slot.cs @@ -0,0 +1,93 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Slot.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.VM.Types; +using System.Collections; +using System.Collections.Generic; + +namespace Neo.VM +{ + /// + /// Used to store local variables, arguments and static fields in the VM. + /// + public class Slot : IReadOnlyList + { + private readonly ReferenceCounter referenceCounter; + private readonly StackItem[] items; + + /// + /// Gets the item at the specified index in the slot. + /// + /// The zero-based index of the item to get. + /// The item at the specified index in the slot. + public StackItem this[int index] + { + get + { + return items[index]; + } + internal set + { + ref var oldValue = ref items[index]; + referenceCounter.RemoveStackReference(oldValue); + oldValue = value; + referenceCounter.AddStackReference(value); + } + } + + /// + /// Gets the number of items in the slot. + /// + public int Count => items.Length; + + /// + /// Creates a slot containing the specified items. + /// + /// The items to be contained. + /// The reference counter to be used. + public Slot(StackItem[] items, ReferenceCounter referenceCounter) + { + this.referenceCounter = referenceCounter; + this.items = items; + foreach (StackItem item in items) + referenceCounter.AddStackReference(item); + } + + /// + /// Create a slot of the specified size. + /// + /// Indicates the number of items contained in the slot. + /// The reference counter to be used. + public Slot(int count, ReferenceCounter referenceCounter) + { + this.referenceCounter = referenceCounter; + items = new StackItem[count]; + System.Array.Fill(items, StackItem.Null); + referenceCounter.AddStackReference(StackItem.Null, count); + } + + internal void ClearReferences() + { + foreach (StackItem item in items) + referenceCounter.RemoveStackReference(item); + } + + IEnumerator IEnumerable.GetEnumerator() + { + foreach (StackItem item in items) yield return item; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return items.GetEnumerator(); + } + } +} diff --git a/src/Neo.VM/StronglyConnectedComponents/Tarjan.cs b/src/Neo.VM/StronglyConnectedComponents/Tarjan.cs new file mode 100644 index 0000000000..5cb7531ab6 --- /dev/null +++ b/src/Neo.VM/StronglyConnectedComponents/Tarjan.cs @@ -0,0 +1,125 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Tarjan.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Collections.Generic; +using T = Neo.VM.Types.StackItem; + +namespace Neo.VM.StronglyConnectedComponents +{ + class Tarjan + { + private readonly IEnumerable vertexs; + private readonly LinkedList> components = new(); + private readonly Stack stack = new(); + private int index = 0; + + public Tarjan(IEnumerable vertexs) + { + this.vertexs = vertexs; + } + + public LinkedList> Invoke() + { + foreach (var v in vertexs) + { + if (v.DFN < 0) + { + StrongConnectNonRecursive(v); + } + } + return components; + } + + private void StrongConnect(T v) + { + v.DFN = v.LowLink = ++index; + stack.Push(v); + v.OnStack = true; + + foreach (T w in v.Successors) + { + if (w.DFN < 0) + { + StrongConnect(w); + v.LowLink = Math.Min(v.LowLink, w.LowLink); + } + else if (w.OnStack) + { + v.LowLink = Math.Min(v.LowLink, w.DFN); + } + } + + if (v.LowLink == v.DFN) + { + HashSet scc = new(ReferenceEqualityComparer.Instance); + T w; + do + { + w = stack.Pop(); + w.OnStack = false; + scc.Add(w); + } while (v != w); + components.AddLast(scc); + } + } + + private void StrongConnectNonRecursive(T v) + { + Stack<(T node, T?, IEnumerator?, int)> sstack = new(); + sstack.Push((v, null, null, 0)); + while (sstack.TryPop(out var state)) + { + v = state.node; + var (_, w, s, n) = state; + switch (n) + { + case 0: + v.DFN = v.LowLink = ++index; + stack.Push(v); + v.OnStack = true; + s = v.Successors.GetEnumerator(); + goto case 2; + case 1: + v.LowLink = Math.Min(v.LowLink, w!.LowLink); + goto case 2; + case 2: + while (s!.MoveNext()) + { + w = s.Current; + if (w.DFN < 0) + { + sstack.Push((v, w, s, 1)); + v = w; + goto case 0; + } + else if (w.OnStack) + { + v.LowLink = Math.Min(v.LowLink, w.DFN); + } + } + if (v.LowLink == v.DFN) + { + HashSet scc = new(ReferenceEqualityComparer.Instance); + do + { + w = stack.Pop(); + w.OnStack = false; + scc.Add(w); + } while (v != w); + components.AddLast(scc); + } + break; + } + } + } + } +} diff --git a/src/Neo.VM/Types/Array.cs b/src/Neo.VM/Types/Array.cs new file mode 100644 index 0000000000..e15358a881 --- /dev/null +++ b/src/Neo.VM/Types/Array.cs @@ -0,0 +1,146 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Array.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Neo.VM.Types +{ + /// + /// Represents an array or a complex object in the VM. + /// + public class Array : CompoundType, IReadOnlyList + { + protected readonly List _array; + + /// + /// Get or set item in the array. + /// + /// The index of the item in the array. + /// The item at the specified index. + public StackItem this[int index] + { + get => _array[index]; + set + { + if (IsReadOnly) throw new InvalidOperationException("The object is readonly."); + ReferenceCounter?.RemoveReference(_array[index], this); + _array[index] = value; + ReferenceCounter?.AddReference(value, this); + } + } + + /// + /// The number of items in the array. + /// + public override int Count => _array.Count; + public override IEnumerable SubItems => _array; + public override int SubItemsCount => _array.Count; + public override StackItemType Type => StackItemType.Array; + + /// + /// Create an array containing the specified items. + /// + /// The items to be included in the array. + public Array(IEnumerable? items = null) + : this(null, items) + { + } + + /// + /// Create an array containing the specified items. And make the array use the specified . + /// + /// The to be used by this array. + /// The items to be included in the array. + public Array(ReferenceCounter? referenceCounter, IEnumerable? items = null) + : base(referenceCounter) + { + _array = items switch + { + null => new List(), + List list => list, + _ => new List(items) + }; + if (referenceCounter != null) + foreach (StackItem item in _array) + referenceCounter.AddReference(item, this); + } + + /// + /// Add a new item at the end of the array. + /// + /// The item to be added. + public void Add(StackItem item) + { + if (IsReadOnly) throw new InvalidOperationException("The object is readonly."); + _array.Add(item); + ReferenceCounter?.AddReference(item, this); + } + + public override void Clear() + { + if (IsReadOnly) throw new InvalidOperationException("The object is readonly."); + if (ReferenceCounter != null) + foreach (StackItem item in _array) + ReferenceCounter.RemoveReference(item, this); + _array.Clear(); + } + + public override StackItem ConvertTo(StackItemType type) + { + if (Type == StackItemType.Array && type == StackItemType.Struct) + return new Struct(ReferenceCounter, new List(_array)); + return base.ConvertTo(type); + } + + internal sealed override StackItem DeepCopy(Dictionary refMap, bool asImmutable) + { + if (refMap.TryGetValue(this, out StackItem? mappedItem)) return mappedItem; + Array result = this is Struct ? new Struct(ReferenceCounter) : new Array(ReferenceCounter); + refMap.Add(this, result); + foreach (StackItem item in _array) + result.Add(item.DeepCopy(refMap, asImmutable)); + result.IsReadOnly = true; + return result; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public IEnumerator GetEnumerator() + { + return _array.GetEnumerator(); + } + + /// + /// Remove the item at the specified index. + /// + /// The index of the item to be removed. + public void RemoveAt(int index) + { + if (IsReadOnly) throw new InvalidOperationException("The object is readonly."); + ReferenceCounter?.RemoveReference(_array[index], this); + _array.RemoveAt(index); + } + + /// + /// Reverse all items in the array. + /// + public void Reverse() + { + if (IsReadOnly) throw new InvalidOperationException("The object is readonly."); + _array.Reverse(); + } + } +} diff --git a/src/Neo.VM/Types/Boolean.cs b/src/Neo.VM/Types/Boolean.cs new file mode 100644 index 0000000000..e0a9864d92 --- /dev/null +++ b/src/Neo.VM/Types/Boolean.cs @@ -0,0 +1,76 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Boolean.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Neo.VM.Types +{ + /// + /// Represents a boolean ( or ) value in the VM. + /// + [DebuggerDisplay("Type={GetType().Name}, Value={value}")] + public class Boolean : PrimitiveType + { + private static readonly ReadOnlyMemory TRUE = new byte[] { 1 }; + private static readonly ReadOnlyMemory FALSE = new byte[] { 0 }; + + private readonly bool value; + + public override ReadOnlyMemory Memory => value ? TRUE : FALSE; + public override int Size => sizeof(bool); + public override StackItemType Type => StackItemType.Boolean; + + /// + /// Create a new VM object representing the boolean type. + /// + /// The initial value of the object. + internal Boolean(bool value) + { + this.value = value; + } + + public override bool Equals(StackItem? other) + { + if (ReferenceEquals(this, other)) return true; + if (other is Boolean b) return value == b.value; + return false; + } + + public override bool GetBoolean() + { + return value; + } + + public override int GetHashCode() + { + return HashCode.Combine(value); + } + + public override BigInteger GetInteger() + { + return value ? BigInteger.One : BigInteger.Zero; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Boolean(bool value) + { + return value ? True : False; + } + + public override string ToString() + { + return value.ToString(); + } + } +} diff --git a/src/Neo.VM/Types/Buffer.cs b/src/Neo.VM/Types/Buffer.cs new file mode 100644 index 0000000000..8d170577e6 --- /dev/null +++ b/src/Neo.VM/Types/Buffer.cs @@ -0,0 +1,116 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Buffer.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics; +using System.Numerics; + +namespace Neo.VM.Types +{ + /// + /// Represents a memory block that can be used for reading and writing in the VM. + /// + [DebuggerDisplay("Type={GetType().Name}, Value={System.Convert.ToHexString(GetSpan())}")] + public class Buffer : StackItem + { + /// + /// The internal byte array used to store the actual data. + /// + public readonly Memory InnerBuffer; + + /// + /// The size of the buffer. + /// + public int Size => InnerBuffer.Length; + public override StackItemType Type => StackItemType.Buffer; + + private readonly byte[] _buffer; + private bool _keep_alive = false; + + /// + /// Create a buffer of the specified size. + /// + /// The size of this buffer. + /// Indicates whether the created buffer is zero-initialized. + public Buffer(int size, bool zeroInitialize = true) + { + _buffer = ArrayPool.Shared.Rent(size); + InnerBuffer = new Memory(_buffer, 0, size); + if (zeroInitialize) InnerBuffer.Span.Clear(); + } + + /// + /// Create a buffer with the specified data. + /// + /// The data to be contained in this buffer. + public Buffer(ReadOnlySpan data) : this(data.Length, false) + { + data.CopyTo(InnerBuffer.Span); + } + + internal override void Cleanup() + { + if (!_keep_alive) + ArrayPool.Shared.Return(_buffer, clearArray: false); + } + + public void KeepAlive() + { + _keep_alive = true; + } + + public override StackItem ConvertTo(StackItemType type) + { + switch (type) + { + case StackItemType.Integer: + if (InnerBuffer.Length > Integer.MaxSize) + throw new InvalidCastException(); + return new BigInteger(InnerBuffer.Span); + case StackItemType.ByteString: +#if NET5_0_OR_GREATER + byte[] clone = GC.AllocateUninitializedArray(InnerBuffer.Length); +#else + byte[] clone = new byte[InnerBuffer.Length]; +#endif + InnerBuffer.CopyTo(clone); + return clone; + default: + return base.ConvertTo(type); + } + } + + internal override StackItem DeepCopy(Dictionary refMap, bool asImmutable) + { + if (refMap.TryGetValue(this, out StackItem? mappedItem)) return mappedItem; + StackItem result = asImmutable ? new ByteString(InnerBuffer.ToArray()) : new Buffer(InnerBuffer.Span); + refMap.Add(this, result); + return result; + } + + public override bool GetBoolean() + { + return true; + } + + public override ReadOnlySpan GetSpan() + { + return InnerBuffer.Span; + } + + public override string ToString() + { + return GetSpan().TryGetString(out var str) ? $"(\"{str}\")" : $"(\"Base64: {Convert.ToBase64String(GetSpan())}\")"; + } + } +} diff --git a/src/Neo.VM/Types/ByteString.cs b/src/Neo.VM/Types/ByteString.cs new file mode 100644 index 0000000000..2d2df2d862 --- /dev/null +++ b/src/Neo.VM/Types/ByteString.cs @@ -0,0 +1,142 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ByteString.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.VM.Cryptography; +using System; +using System.Buffers.Binary; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Neo.VM.Types +{ + /// + /// Represents an immutable memory block in the VM. + /// + [DebuggerDisplay("Type={GetType().Name}, Value={System.Convert.ToHexString(GetSpan())}")] + public class ByteString : PrimitiveType + { + /// + /// An empty . + /// + public static readonly ByteString Empty = ReadOnlyMemory.Empty; + + private static readonly uint s_seed = unchecked((uint)new Random().Next()); + private int _hashCode = 0; + + public override ReadOnlyMemory Memory { get; } + public override StackItemType Type => StackItemType.ByteString; + + /// + /// Create a new with the specified data. + /// + /// The data to be contained in this . + public ByteString(ReadOnlyMemory data) + { + Memory = data; + } + + private bool Equals(ByteString other) + { + return GetSpan().SequenceEqual(other.GetSpan()); + } + + public override bool Equals(StackItem? other) + { + if (ReferenceEquals(this, other)) return true; + if (other is not ByteString b) return false; + return Equals(b); + } + + internal override bool Equals(StackItem? other, ExecutionEngineLimits limits) + { + uint maxComparableSize = limits.MaxComparableSize; + return Equals(other, ref maxComparableSize); + } + + internal bool Equals(StackItem? other, ref uint limits) + { + if (Size > limits || limits == 0) + throw new InvalidOperationException("The operand exceeds the maximum comparable size."); + uint comparedSize = 1; + try + { + if (other is not ByteString b) return false; + comparedSize = Math.Max((uint)Math.Max(Size, b.Size), comparedSize); + if (ReferenceEquals(this, b)) return true; + if (b.Size > limits) + throw new InvalidOperationException("The operand exceeds the maximum comparable size."); + return Equals(b); + } + finally + { + limits -= comparedSize; + } + } + + public override bool GetBoolean() + { + if (Size > Integer.MaxSize) throw new InvalidCastException(); + return Unsafe.NotZero(GetSpan()); + } + + public override int GetHashCode() + { + if (_hashCode == 0) + { + using Murmur32 murmur = new(s_seed); + _hashCode = BinaryPrimitives.ReadInt32LittleEndian(murmur.ComputeHash(GetSpan().ToArray())); + } + return _hashCode; + } + + public override BigInteger GetInteger() + { + if (Size > Integer.MaxSize) throw new InvalidCastException($"MaxSize exceed: {Size}"); + return new BigInteger(GetSpan()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlyMemory(ByteString value) + { + return value.Memory; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ReadOnlySpan(ByteString value) + { + return value.Memory.Span; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ByteString(byte[] value) + { + return new ByteString(value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ByteString(ReadOnlyMemory value) + { + return new ByteString(value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator ByteString(string value) + { + return new ByteString(Utility.StrictUTF8.GetBytes(value)); + } + + public override string ToString() + { + return GetSpan().TryGetString(out var str) ? $"\"{str}\"" : $"\"Base64: {Convert.ToBase64String(GetSpan())}\""; + } + } +} diff --git a/src/Neo.VM/Types/CompoundType.cs b/src/Neo.VM/Types/CompoundType.cs new file mode 100644 index 0000000000..ede743b881 --- /dev/null +++ b/src/Neo.VM/Types/CompoundType.cs @@ -0,0 +1,76 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// CompoundType.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Neo.VM.Types +{ + /// + /// The base class for complex types in the VM. + /// + [DebuggerDisplay("Type={GetType().Name}, Count={Count}, Id={System.Collections.Generic.ReferenceEqualityComparer.Instance.GetHashCode(this)}")] + public abstract class CompoundType : StackItem + { + /// + /// The reference counter used to count the items in the VM object. + /// + protected readonly ReferenceCounter? ReferenceCounter; + + /// + /// Create a new with the specified reference counter. + /// + /// The reference counter to be used. + protected CompoundType(ReferenceCounter? referenceCounter) + { + ReferenceCounter = referenceCounter; + referenceCounter?.AddZeroReferred(this); + } + + /// + /// The number of items in this VM object. + /// + public abstract int Count { get; } + + public abstract IEnumerable SubItems { get; } + + public abstract int SubItemsCount { get; } + + public bool IsReadOnly { get; protected set; } + + /// + /// Remove all items from the VM object. + /// + public abstract void Clear(); + + internal abstract override StackItem DeepCopy(Dictionary refMap, bool asImmutable); + + public sealed override bool GetBoolean() + { + return true; + } + + /// + /// The operation is not supported. Always throw . + /// + /// This method always throws the exception. + public override int GetHashCode() + { + throw new NotSupportedException(); + } + + public override string ToString() + { + return Count.ToString(); + } + } +} diff --git a/src/Neo.VM/Types/Integer.cs b/src/Neo.VM/Types/Integer.cs new file mode 100644 index 0000000000..4d997d6c5a --- /dev/null +++ b/src/Neo.VM/Types/Integer.cs @@ -0,0 +1,139 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Integer.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Neo.VM.Types +{ + /// + /// Represents an integer value in the VM. + /// + [DebuggerDisplay("Type={GetType().Name}, Value={value}")] + public class Integer : PrimitiveType + { + /// + /// The maximum size of an integer in bytes. + /// + public const int MaxSize = 32; + + /// + /// Represents the number 0. + /// + public static readonly Integer Zero = 0; + private readonly BigInteger value; + + public override ReadOnlyMemory Memory => value.IsZero ? ReadOnlyMemory.Empty : value.ToByteArray(); + public override int Size { get; } + public override StackItemType Type => StackItemType.Integer; + + /// + /// Create an integer with the specified value. + /// + /// The value of the integer. + public Integer(BigInteger value) + { + if (value.IsZero) + { + Size = 0; + } + else + { + Size = value.GetByteCount(); + if (Size > MaxSize) throw new ArgumentException($"MaxSize exceed: {Size}"); + } + this.value = value; + } + + public override bool Equals(StackItem? other) + { + if (ReferenceEquals(this, other)) return true; + if (other is Integer i) return value == i.value; + return false; + } + + public override bool GetBoolean() + { + return !value.IsZero; + } + + public override int GetHashCode() + { + return HashCode.Combine(value); + } + + public override BigInteger GetInteger() + { + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Integer(sbyte value) + { + return (BigInteger)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Integer(byte value) + { + return (BigInteger)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Integer(short value) + { + return (BigInteger)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Integer(ushort value) + { + return (BigInteger)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Integer(int value) + { + return (BigInteger)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Integer(uint value) + { + return (BigInteger)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Integer(long value) + { + return (BigInteger)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Integer(ulong value) + { + return (BigInteger)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Integer(BigInteger value) + { + return new Integer(value); + } + + public override string ToString() + { + return value.ToString(); + } + } +} diff --git a/src/Neo.VM/Types/InteropInterface.cs b/src/Neo.VM/Types/InteropInterface.cs new file mode 100644 index 0000000000..ee1998316a --- /dev/null +++ b/src/Neo.VM/Types/InteropInterface.cs @@ -0,0 +1,69 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// InteropInterface.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Diagnostics; + +namespace Neo.VM.Types +{ + /// + /// Represents an interface used to interoperate with the outside of the the VM. + /// + [DebuggerDisplay("Type={GetType().Name}, Value={_object}")] + public class InteropInterface : StackItem + { + private readonly object _object; + + public override StackItemType Type => StackItemType.InteropInterface; + + /// + /// Create an interoperability interface that wraps the specified . + /// + /// The wrapped . + public InteropInterface(object value) + { + _object = value ?? throw new ArgumentNullException(nameof(value)); + } + + public override bool Equals(StackItem? other) + { + if (ReferenceEquals(this, other)) return true; + if (other is InteropInterface i) return _object.Equals(i._object); + return false; + } + + public override bool GetBoolean() + { + return true; + } + + public override int GetHashCode() + { + return HashCode.Combine(_object); + } + + public override T GetInterface() + { + if (_object is T t) return t; + throw new InvalidCastException($"The item can't be casted to type {typeof(T)}"); + } + + internal object GetInterface() + { + return _object; + } + + public override string ToString() + { + return _object.ToString() ?? "NULL"; + } + } +} diff --git a/src/Neo.VM/Types/Map.cs b/src/Neo.VM/Types/Map.cs new file mode 100644 index 0000000000..5df66fb897 --- /dev/null +++ b/src/Neo.VM/Types/Map.cs @@ -0,0 +1,181 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Map.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.VM.Collections; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace Neo.VM.Types +{ + /// + /// Represents an ordered collection of key-value pairs in the VM. + /// + public class Map : CompoundType, IReadOnlyDictionary + { + /// + /// Indicates the maximum size of keys in bytes. + /// + public const int MaxKeySize = 64; + + private readonly OrderedDictionary dictionary = new(); + + /// + /// Gets or sets the element that has the specified key in the map. + /// + /// The key to locate. + /// The element that has the specified key in the map. + public StackItem this[PrimitiveType key] + { + get + { + if (key.Size > MaxKeySize) + throw new ArgumentException($"MaxKeySize exceed: {key.Size}"); + return dictionary[key]; + } + set + { + if (key.Size > MaxKeySize) + throw new ArgumentException($"MaxKeySize exceed: {key.Size}"); + if (IsReadOnly) throw new InvalidOperationException("The object is readonly."); + if (ReferenceCounter != null) + { + if (dictionary.TryGetValue(key, out StackItem? old_value)) + ReferenceCounter.RemoveReference(old_value, this); + else + ReferenceCounter.AddReference(key, this); + ReferenceCounter.AddReference(value, this); + } + dictionary[key] = value; + } + } + + public override int Count => dictionary.Count; + + /// + /// Gets an enumerable collection that contains the keys in the map. + /// + public IEnumerable Keys => dictionary.Keys; + + public override IEnumerable SubItems => Keys.Concat(Values); + + public override int SubItemsCount => dictionary.Count * 2; + + public override StackItemType Type => StackItemType.Map; + + /// + /// Gets an enumerable collection that contains the values in the map. + /// + public IEnumerable Values => dictionary.Values; + + /// + /// Create a new map with the specified reference counter. + /// + /// The reference counter to be used. + public Map(ReferenceCounter? referenceCounter = null) + : base(referenceCounter) + { + } + + public override void Clear() + { + if (IsReadOnly) throw new InvalidOperationException("The object is readonly."); + if (ReferenceCounter != null) + foreach (var pair in dictionary) + { + ReferenceCounter.RemoveReference(pair.Key, this); + ReferenceCounter.RemoveReference(pair.Value, this); + } + dictionary.Clear(); + } + + /// + /// Determines whether the map contains an element that has the specified key. + /// + /// The key to locate. + /// + /// if the map contains an element that has the specified key; + /// otherwise, . + /// + public bool ContainsKey(PrimitiveType key) + { + if (key.Size > MaxKeySize) + throw new ArgumentException($"MaxKeySize exceed: {key.Size}"); + return dictionary.ContainsKey(key); + } + + internal override StackItem DeepCopy(Dictionary refMap, bool asImmutable) + { + if (refMap.TryGetValue(this, out StackItem? mappedItem)) return mappedItem; + Map result = new(ReferenceCounter); + refMap.Add(this, result); + foreach (var (k, v) in dictionary) + result[k] = v.DeepCopy(refMap, asImmutable); + result.IsReadOnly = true; + return result; + } + + IEnumerator> IEnumerable>.GetEnumerator() + { + return ((IDictionary)dictionary).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IDictionary)dictionary).GetEnumerator(); + } + + /// + /// Removes the element with the specified key from the map. + /// + /// The key of the element to remove. + /// + /// if the element is successfully removed; + /// otherwise, . + /// This method also returns if was not found in the original map. + /// + public bool Remove(PrimitiveType key) + { + if (key.Size > MaxKeySize) + throw new ArgumentException($"MaxKeySize exceed: {key.Size}"); + if (IsReadOnly) throw new InvalidOperationException("The object is readonly."); + if (!dictionary.Remove(key, out StackItem? old_value)) + return false; + ReferenceCounter?.RemoveReference(key, this); + ReferenceCounter?.RemoveReference(old_value, this); + return true; + } + + /// + /// Gets the value that is associated with the specified key. + /// + /// The key to locate. + /// + /// When this method returns, the value associated with the specified key, if the key is found; + /// otherwise, . + /// + /// + /// if the map contains an element that has the specified key; + /// otherwise, . + /// +// supress warning of value parameter nullability mismatch +#pragma warning disable CS8767 + public bool TryGetValue(PrimitiveType key, [MaybeNullWhen(false)] out StackItem value) +#pragma warning restore CS8767 + { + if (key.Size > MaxKeySize) + throw new ArgumentException($"MaxKeySize exceed: {key.Size}"); + return dictionary.TryGetValue(key, out value); + } + } +} diff --git a/src/Neo.VM/Types/Null.cs b/src/Neo.VM/Types/Null.cs new file mode 100644 index 0000000000..238d8b2389 --- /dev/null +++ b/src/Neo.VM/Types/Null.cs @@ -0,0 +1,65 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Null.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Neo.VM.Types +{ + /// + /// Represents in the VM. + /// + public class Null : StackItem + { + public override StackItemType Type => StackItemType.Any; + + internal Null() { } + + public override StackItem ConvertTo(StackItemType type) + { + if (type == StackItemType.Any || !Enum.IsDefined(typeof(StackItemType), type)) + throw new InvalidCastException($"Type can't be converted to StackItemType: {type}"); + return this; + } + + public override bool Equals(StackItem? other) + { + if (ReferenceEquals(this, other)) return true; + return other is Null; + } + + public override bool GetBoolean() + { + return false; + } + + public override int GetHashCode() + { + return 0; + } + + [return: MaybeNull] + public override T GetInterface() + { + return default; + } + + public override string? GetString() + { + return null; + } + + public override string ToString() + { + return "NULL"; + } + } +} diff --git a/src/Neo.VM/Types/Pointer.cs b/src/Neo.VM/Types/Pointer.cs new file mode 100644 index 0000000000..1f729c812f --- /dev/null +++ b/src/Neo.VM/Types/Pointer.cs @@ -0,0 +1,68 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Pointer.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Diagnostics; + +namespace Neo.VM.Types +{ + /// + /// Represents the instruction pointer in the VM, used as the target of jump instructions. + /// + [DebuggerDisplay("Type={GetType().Name}, Position={Position}")] + public class Pointer : StackItem + { + /// + /// The object containing this pointer. + /// + public Script Script { get; } + + /// + /// The position of the pointer in the script. + /// + public int Position { get; } + + public override StackItemType Type => StackItemType.Pointer; + + /// + /// Create a code pointer with the specified script and position. + /// + /// The object containing this pointer. + /// The position of the pointer in the script. + public Pointer(Script script, int position) + { + Script = script; + Position = position; + } + + public override bool Equals(StackItem? other) + { + if (other == this) return true; + if (other is Pointer p) return Position == p.Position && Script == p.Script; + return false; + } + + public override bool GetBoolean() + { + return true; + } + + public override int GetHashCode() + { + return HashCode.Combine(Script, Position); + } + + public override string ToString() + { + return Position.ToString(); + } + } +} diff --git a/src/Neo.VM/Types/PrimitiveType.cs b/src/Neo.VM/Types/PrimitiveType.cs new file mode 100644 index 0000000000..8415b8a45c --- /dev/null +++ b/src/Neo.VM/Types/PrimitiveType.cs @@ -0,0 +1,139 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// PrimitiveType.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Neo.VM.Types +{ + /// + /// The base class for primitive types in the VM. + /// + public abstract class PrimitiveType : StackItem + { + public abstract ReadOnlyMemory Memory { get; } + + /// + /// The size of the VM object in bytes. + /// + public virtual int Size => Memory.Length; + + public override StackItem ConvertTo(StackItemType type) + { + if (type == Type) return this; + return type switch + { + StackItemType.Integer => GetInteger(), + StackItemType.ByteString => Memory, + StackItemType.Buffer => new Buffer(GetSpan()), + _ => base.ConvertTo(type) + }; + } + + internal sealed override StackItem DeepCopy(Dictionary refMap, bool asImmutable) + { + return this; + } + + public abstract override bool Equals(StackItem? other); + + /// + /// Get the hash code of the VM object, which is used for key comparison in the . + /// + /// The hash code of this VM object. + public abstract override int GetHashCode(); + + public sealed override ReadOnlySpan GetSpan() + { + return Memory.Span; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator PrimitiveType(sbyte value) + { + return (Integer)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator PrimitiveType(byte value) + { + return (Integer)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator PrimitiveType(short value) + { + return (Integer)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator PrimitiveType(ushort value) + { + return (Integer)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator PrimitiveType(int value) + { + return (Integer)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator PrimitiveType(uint value) + { + return (Integer)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator PrimitiveType(long value) + { + return (Integer)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator PrimitiveType(ulong value) + { + return (Integer)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator PrimitiveType(BigInteger value) + { + return (Integer)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator PrimitiveType(bool value) + { + return (Boolean)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator PrimitiveType(byte[] value) + { + return (ByteString)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator PrimitiveType(ReadOnlyMemory value) + { + return (ByteString)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator PrimitiveType(string value) + { + return (ByteString)value; + } + } +} diff --git a/src/Neo.VM/Types/StackItem.Vertex.cs b/src/Neo.VM/Types/StackItem.Vertex.cs new file mode 100644 index 0000000000..a988d5db32 --- /dev/null +++ b/src/Neo.VM/Types/StackItem.Vertex.cs @@ -0,0 +1,40 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// StackItem.Vertex.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Neo.VM.Types +{ + partial class StackItem + { + internal class ObjectReferenceEntry + { + public StackItem Item; + public int References; + public ObjectReferenceEntry(StackItem item) => Item = item; + } + + internal int StackReferences = 0; + internal Dictionary? ObjectReferences; + internal int DFN = -1; + internal int LowLink = 0; + internal bool OnStack = false; + + internal IEnumerable Successors => ObjectReferences?.Values.Where(p => p.References > 0).Select(p => p.Item) ?? System.Array.Empty(); + + internal void Reset() => (DFN, LowLink, OnStack) = (-1, 0, false); + + public override int GetHashCode() => + HashCode.Combine(GetSpan().ToArray()); + } +} diff --git a/src/Neo.VM/Types/StackItem.cs b/src/Neo.VM/Types/StackItem.cs new file mode 100644 index 0000000000..2d85becacc --- /dev/null +++ b/src/Neo.VM/Types/StackItem.cs @@ -0,0 +1,262 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// StackItem.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +#pragma warning disable CS0659 + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Neo.VM.Types +{ + /// + /// The base class for all types in the VM. + /// + public abstract partial class StackItem : IEquatable + { + [ThreadStatic] + private static Boolean? tls_true = null; + + /// + /// Represents in the VM. + /// + public static Boolean True + { + get + { + tls_true ??= new(true); + return tls_true; + } + } + + [ThreadStatic] + private static Boolean? tls_false = null; + + /// + /// Represents in the VM. + /// + public static Boolean False + { + get + { + tls_false ??= new(false); + return tls_false; + } + } + + [ThreadStatic] + private static Null? tls_null = null; + + /// + /// Represents in the VM. + /// + public static StackItem Null + { + get + { + tls_null ??= new(); + return tls_null; + } + } + + /// + /// Indicates whether the object is . + /// + public bool IsNull => this is Null; + + /// + /// The type of this VM object. + /// + public abstract StackItemType Type { get; } + + /// + /// Convert the VM object to the specified type. + /// + /// The type to be converted to. + /// The converted object. + public virtual StackItem ConvertTo(StackItemType type) + { + if (type == Type) return this; + if (type == StackItemType.Boolean) return GetBoolean(); + throw new InvalidCastException(); + } + + internal virtual void Cleanup() + { + } + + /// + /// Copy the object and all its children. + /// + /// The copied object. + public StackItem DeepCopy(bool asImmutable = false) + { + return DeepCopy(new(ReferenceEqualityComparer.Instance), asImmutable); + } + + internal virtual StackItem DeepCopy(Dictionary refMap, bool asImmutable) + { + return this; + } + + public sealed override bool Equals(object? obj) + { + if (ReferenceEquals(this, obj)) return true; + if (obj is StackItem item) return Equals(item); + return false; + } + + public virtual bool Equals(StackItem? other) + { + return ReferenceEquals(this, other); + } + + internal virtual bool Equals(StackItem? other, ExecutionEngineLimits limits) + { + return Equals(other); + } + + /// + /// Wrap the specified and return an containing the . + /// + /// The wrapped . + /// + public static StackItem FromInterface(object? value) + { + if (value is null) return Null; + return new InteropInterface(value); + } + + /// + /// Get the boolean value represented by the VM object. + /// + /// The boolean value represented by the VM object. + public abstract bool GetBoolean(); + + /// + /// Get the integer value represented by the VM object. + /// + /// The integer value represented by the VM object. + public virtual BigInteger GetInteger() + { + throw new InvalidCastException(); + } + + /// + /// Get the wrapped by this interface and convert it to the specified type. + /// + /// The type to convert to. + /// The wrapped . + [return: MaybeNull] + public virtual T GetInterface() where T : notnull + { + throw new InvalidCastException(); + } + + /// + /// Get the readonly span used to read the VM object data. + /// + /// + public virtual ReadOnlySpan GetSpan() + { + throw new InvalidCastException(); + } + + /// + /// Get the value represented by the VM object. + /// + /// The value represented by the VM object. + public virtual string? GetString() + { + return Utility.StrictUTF8.GetString(GetSpan()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator StackItem(sbyte value) + { + return (Integer)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator StackItem(byte value) + { + return (Integer)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator StackItem(short value) + { + return (Integer)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator StackItem(ushort value) + { + return (Integer)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator StackItem(int value) + { + return (Integer)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator StackItem(uint value) + { + return (Integer)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator StackItem(long value) + { + return (Integer)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator StackItem(ulong value) + { + return (Integer)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator StackItem(BigInteger value) + { + return (Integer)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator StackItem(bool value) + { + return value ? True : False; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator StackItem(byte[] value) + { + return (ByteString)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator StackItem(ReadOnlyMemory value) + { + return (ByteString)value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator StackItem(string value) + { + return (ByteString)value; + } + } +} diff --git a/src/Neo.VM/Types/StackItemType.cs b/src/Neo.VM/Types/StackItemType.cs new file mode 100644 index 0000000000..c65921e1ac --- /dev/null +++ b/src/Neo.VM/Types/StackItemType.cs @@ -0,0 +1,69 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// StackItemType.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.VM.Types +{ + /// + /// An enumeration representing the types in the VM. + /// + public enum StackItemType : byte + { + /// + /// Represents any type. + /// + Any = 0x00, + + /// + /// Represents a code pointer. + /// + Pointer = 0x10, + + /// + /// Represents the boolean ( or ) type. + /// + Boolean = 0x20, + + /// + /// Represents an integer. + /// + Integer = 0x21, + + /// + /// Represents an immutable memory block. + /// + ByteString = 0x28, + + /// + /// Represents a memory block that can be used for reading and writing. + /// + Buffer = 0x30, + + /// + /// Represents an array or a complex object. + /// + Array = 0x40, + + /// + /// Represents a structure. + /// + Struct = 0x41, + + /// + /// Represents an ordered collection of key-value pairs. + /// + Map = 0x48, + + /// + /// Represents an interface used to interoperate with the outside of the the VM. + /// + InteropInterface = 0x60, + } +} diff --git a/src/Neo.VM/Types/Struct.cs b/src/Neo.VM/Types/Struct.cs new file mode 100644 index 0000000000..8170414363 --- /dev/null +++ b/src/Neo.VM/Types/Struct.cs @@ -0,0 +1,134 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Struct.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Collections.Generic; + +namespace Neo.VM.Types +{ + /// + /// Represents a structure in the VM. + /// + public class Struct : Array + { + public override StackItemType Type => StackItemType.Struct; + + /// + /// Create a structure with the specified fields. + /// + /// The fields to be included in the structure. + public Struct(IEnumerable? fields = null) + : this(null, fields) + { + } + + /// + /// Create a structure with the specified fields. And make the structure use the specified . + /// + /// The to be used by this structure. + /// The fields to be included in the structure. + public Struct(ReferenceCounter? referenceCounter, IEnumerable? fields = null) + : base(referenceCounter, fields) + { + } + + /// + /// Create a new structure with the same content as this structure. All nested structures will be copied by value. + /// + /// Execution engine limits + /// The copied structure. + public Struct Clone(ExecutionEngineLimits limits) + { + int count = (int)(limits.MaxStackSize - 1); + Struct result = new(ReferenceCounter); + Queue queue = new(); + queue.Enqueue(result); + queue.Enqueue(this); + while (queue.Count > 0) + { + Struct a = queue.Dequeue(); + Struct b = queue.Dequeue(); + foreach (StackItem item in b) + { + count--; + if (count < 0) throw new InvalidOperationException("Beyond clone limits!"); + if (item is Struct sb) + { + Struct sa = new(ReferenceCounter); + a.Add(sa); + queue.Enqueue(sa); + queue.Enqueue(sb); + } + else + { + a.Add(item); + } + } + } + return result; + } + + public override StackItem ConvertTo(StackItemType type) + { + if (type == StackItemType.Array) + return new Array(ReferenceCounter, new List(_array)); + return base.ConvertTo(type); + } + + public override bool Equals(StackItem? other) + { + throw new NotSupportedException(); + } + + internal override bool Equals(StackItem? other, ExecutionEngineLimits limits) + { + if (other is not Struct s) return false; + Stack stack1 = new(); + Stack stack2 = new(); + stack1.Push(this); + stack2.Push(s); + uint count = limits.MaxStackSize; + uint maxComparableSize = limits.MaxComparableSize; + while (stack1.Count > 0) + { + if (count-- == 0) + throw new InvalidOperationException("Too many struct items to compare."); + StackItem a = stack1.Pop(); + StackItem b = stack2.Pop(); + if (a is ByteString byteString) + { + if (!byteString.Equals(b, ref maxComparableSize)) return false; + } + else + { + if (maxComparableSize == 0) + throw new InvalidOperationException("The operand exceeds the maximum comparable size."); + maxComparableSize -= 1; + if (a is Struct sa) + { + if (ReferenceEquals(a, b)) continue; + if (b is not Struct sb) return false; + if (sa.Count != sb.Count) return false; + foreach (StackItem item in sa) + stack1.Push(item); + foreach (StackItem item in sb) + stack2.Push(item); + } + else + { + if (!a.Equals(b)) return false; + } + } + } + return true; + } + } +} diff --git a/src/Neo.VM/Unsafe.cs b/src/Neo.VM/Unsafe.cs new file mode 100644 index 0000000000..e3f4366be2 --- /dev/null +++ b/src/Neo.VM/Unsafe.cs @@ -0,0 +1,42 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Unsafe.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Runtime.CompilerServices; + +namespace Neo.VM +{ + unsafe internal static class Unsafe + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool NotZero(ReadOnlySpan x) + { + int len = x.Length; + if (len == 0) return false; + fixed (byte* xp = x) + { + long* xlp = (long*)xp; + for (; len >= 8; len -= 8) + { + if (*xlp != 0) return true; + xlp++; + } + byte* xbp = (byte*)xlp; + for (; len > 0; len--) + { + if (*xbp != 0) return true; + xbp++; + } + } + return false; + } + } +} diff --git a/src/Neo.VM/Utility.cs b/src/Neo.VM/Utility.cs new file mode 100644 index 0000000000..7c1852b7f9 --- /dev/null +++ b/src/Neo.VM/Utility.cs @@ -0,0 +1,120 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Utility.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Text; + +namespace Neo.VM +{ + public static class Utility + { + public static Encoding StrictUTF8 { get; } + + static Utility() + { + StrictUTF8 = (Encoding)Encoding.UTF8.Clone(); + StrictUTF8.DecoderFallback = DecoderFallback.ExceptionFallback; + StrictUTF8.EncoderFallback = EncoderFallback.ExceptionFallback; + } + + public static bool TryGetString(this ReadOnlySpan bytes, out string? value) + { + try + { + value = StrictUTF8.GetString(bytes); + return true; + } + catch + { + value = default; + return false; + } + } + + public static BigInteger ModInverse(this BigInteger value, BigInteger modulus) + { + if (value <= 0) throw new ArgumentOutOfRangeException(nameof(value)); + if (modulus < 2) throw new ArgumentOutOfRangeException(nameof(modulus)); + BigInteger r = value, old_r = modulus, s = 1, old_s = 0; + while (r > 0) + { + var q = old_r / r; + (old_r, r) = (r, old_r % r); + (old_s, s) = (s, old_s - q * s); + } + var result = old_s % modulus; + if (result < 0) result += modulus; + if (!(value * result % modulus).IsOne) throw new InvalidOperationException(); + return result; + } + + public static BigInteger Sqrt(this BigInteger value) + { + if (value < 0) throw new InvalidOperationException("value can not be negative"); + if (value.IsZero) return BigInteger.Zero; + if (value < 4) return BigInteger.One; + + var z = value; + var x = BigInteger.One << (int)(((value - 1).GetBitLength() + 1) >> 1); + while (x < z) + { + z = x; + x = (value / x + x) / 2; + } + + return z; + } + + /// + /// Gets the number of bits required for shortest two's complement representation of the current instance without the sign bit. + /// + /// The minimum non-negative number of bits in two's complement notation without the sign bit. + /// This method returns 0 if the value of current object is equal to or . For positive integers the return value is equal to the ordinary binary representation string length. + public static long GetBitLength(this BigInteger value) + { +#if NET5_0_OR_GREATER + return value.GetBitLength(); +#else + if (value == 0 || value == BigInteger.MinusOne) return 0; + + // Note: This method is imprecise and might not work as expected with integers larger than 256 bits. + var b = value.ToByteArray(); + if (b.Length == 1 || (b.Length == 2 && b[1] == 0)) + { + return BitCount(value.Sign > 0 ? b[0] : (byte)(255 - b[0])); + } + return (b.Length - 1) * 8 + BitCount(value.Sign > 0 ? b[^1] : 255 - b[^1]); +#endif + } + +#if !NET5_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int BitCount(int w) + { + return w < 1 << 15 ? (w < 1 << 7 + ? (w < 1 << 3 ? (w < 1 << 1 + ? (w < 1 << 0 ? (w < 0 ? 32 : 0) : 1) + : (w < 1 << 2 ? 2 : 3)) : (w < 1 << 5 + ? (w < 1 << 4 ? 4 : 5) + : (w < 1 << 6 ? 6 : 7))) + : (w < 1 << 11 + ? (w < 1 << 9 ? (w < 1 << 8 ? 8 : 9) : (w < 1 << 10 ? 10 : 11)) + : (w < 1 << 13 ? (w < 1 << 12 ? 12 : 13) : (w < 1 << 14 ? 14 : 15)))) : (w < 1 << 23 ? (w < 1 << 19 + ? (w < 1 << 17 ? (w < 1 << 16 ? 16 : 17) : (w < 1 << 18 ? 18 : 19)) + : (w < 1 << 21 ? (w < 1 << 20 ? 20 : 21) : (w < 1 << 22 ? 22 : 23))) : (w < 1 << 27 + ? (w < 1 << 25 ? (w < 1 << 24 ? 24 : 25) : (w < 1 << 26 ? 26 : 27)) + : (w < 1 << 29 ? (w < 1 << 28 ? 28 : 29) : (w < 1 << 30 ? 30 : 31)))); + } +#endif + } +} diff --git a/src/Neo.VM/VMState.cs b/src/Neo.VM/VMState.cs new file mode 100644 index 0000000000..b9b0fa22d8 --- /dev/null +++ b/src/Neo.VM/VMState.cs @@ -0,0 +1,39 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// VMState.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.VM +{ + /// + /// Indicates the status of the VM. + /// + public enum VMState : byte + { + /// + /// Indicates that the execution is in progress or has not yet begun. + /// + NONE = 0, + + /// + /// Indicates that the execution has been completed successfully. + /// + HALT = 1 << 0, + + /// + /// Indicates that the execution has ended, and an exception that cannot be caught is thrown. + /// + FAULT = 1 << 1, + + /// + /// Indicates that a breakpoint is currently being hit. + /// + BREAK = 1 << 2, + } +} diff --git a/src/Neo.VM/VMUnhandledException.cs b/src/Neo.VM/VMUnhandledException.cs new file mode 100644 index 0000000000..78377059ae --- /dev/null +++ b/src/Neo.VM/VMUnhandledException.cs @@ -0,0 +1,53 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// VMUnhandledException.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.VM.Types; +using System; +using System.Text; +using Array = Neo.VM.Types.Array; + +namespace Neo.VM +{ + /// + /// Represents an unhandled exception in the VM. + /// Thrown when there is an exception in the VM that is not caught by any script. + /// + public class VMUnhandledException : Exception + { + /// + /// The unhandled exception in the VM. + /// + public StackItem ExceptionObject { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The unhandled exception in the VM. + public VMUnhandledException(StackItem ex) : base(GetExceptionMessage(ex)) + { + ExceptionObject = ex; + } + + private static string GetExceptionMessage(StackItem e) + { + StringBuilder sb = new("An unhandled exception was thrown."); + ByteString? s = e as ByteString; + if (s is null && e is Array array && array.Count > 0) + s = array[0] as ByteString; + if (s != null) + { + sb.Append(' '); + sb.Append(Encoding.UTF8.GetString(s.GetSpan())); + } + return sb.ToString(); + } + } +} diff --git a/src/Neo/BigDecimal.cs b/src/Neo/BigDecimal.cs index 0086ca8afb..1d8cf5560e 100644 --- a/src/Neo/BigDecimal.cs +++ b/src/Neo/BigDecimal.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// BigDecimal.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -24,17 +25,17 @@ public struct BigDecimal : IComparable, IEquatable /// /// The value of the number. /// - public BigInteger Value => value; + public readonly BigInteger Value => value; /// /// The number of decimal places for this number. /// - public byte Decimals => decimals; + public readonly byte Decimals => decimals; /// /// The sign of the number. /// - public int Sign => value.Sign; + public readonly int Sign => value.Sign; /// /// Initializes a new instance of the struct. @@ -54,13 +55,13 @@ public BigDecimal(BigInteger value, byte decimals) public unsafe BigDecimal(decimal value) { Span span = stackalloc int[4]; - decimal.GetBits(value, span); + span = decimal.GetBits(value); fixed (int* p = span) { ReadOnlySpan buffer = new(p, 16); this.value = new BigInteger(buffer[..12], isUnsigned: true); if (buffer[15] != 0) this.value = -this.value; - this.decimals = buffer[14]; + decimals = buffer[14]; } } @@ -72,7 +73,7 @@ public unsafe BigDecimal(decimal value) public unsafe BigDecimal(decimal value, byte decimals) { Span span = stackalloc int[4]; - decimal.GetBits(value, span); + span = decimal.GetBits(value); fixed (int* p = span) { ReadOnlySpan buffer = new(p, 16); @@ -92,7 +93,7 @@ public unsafe BigDecimal(decimal value, byte decimals) /// /// The new decimals field. /// The that has the new number of decimal places. - public BigDecimal ChangeDecimals(byte decimals) + public readonly BigDecimal ChangeDecimals(byte decimals) { if (this.decimals == decimals) return this; BigInteger value; @@ -128,7 +129,7 @@ public static BigDecimal Parse(string s, byte decimals) /// Gets a representing the number. /// /// The representing the number. - public override string ToString() + public override readonly string ToString() { BigInteger divisor = BigInteger.Pow(10, decimals); BigInteger result = BigInteger.DivRem(value, divisor, out BigInteger remainder); @@ -181,7 +182,7 @@ public static bool TryParse(string s, byte decimals, out BigDecimal result) return true; } - public int CompareTo(BigDecimal other) + public readonly int CompareTo(BigDecimal other) { BigInteger left = value, right = other.value; if (decimals < other.decimals) @@ -191,18 +192,18 @@ public int CompareTo(BigDecimal other) return left.CompareTo(right); } - public override bool Equals(object obj) + public override readonly bool Equals(object obj) { if (obj is not BigDecimal @decimal) return false; return Equals(@decimal); } - public bool Equals(BigDecimal other) + public readonly bool Equals(BigDecimal other) { return CompareTo(other) == 0; } - public override int GetHashCode() + public override readonly int GetHashCode() { BigInteger divisor = BigInteger.Pow(10, decimals); BigInteger result = BigInteger.DivRem(value, divisor, out BigInteger remainder); diff --git a/src/Neo/ContainsTransactionType.cs b/src/Neo/ContainsTransactionType.cs new file mode 100644 index 0000000000..cb91eb303f --- /dev/null +++ b/src/Neo/ContainsTransactionType.cs @@ -0,0 +1,20 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ContainsTransactionType.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo +{ + public enum ContainsTransactionType + { + NotExist, + ExistsInPool, + ExistsInLedger + } +} diff --git a/src/Neo/Cryptography/Base58.cs b/src/Neo/Cryptography/Base58.cs index 248c581059..adecc8b631 100644 --- a/src/Neo/Cryptography/Base58.cs +++ b/src/Neo/Cryptography/Base58.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// Base58.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Cryptography/BloomFilter.cs b/src/Neo/Cryptography/BloomFilter.cs index 584d46bf0d..73141d9e11 100644 --- a/src/Neo/Cryptography/BloomFilter.cs +++ b/src/Neo/Cryptography/BloomFilter.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// BloomFilter.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -46,10 +47,12 @@ public class BloomFilter public BloomFilter(int m, int k, uint nTweak) { if (k < 0 || m < 0) throw new ArgumentOutOfRangeException(); - this.seeds = Enumerable.Range(0, k).Select(p => (uint)p * 0xFBA4C795 + nTweak).ToArray(); - this.bits = new BitArray(m); - this.bits.Length = m; - this.Tweak = nTweak; + seeds = Enumerable.Range(0, k).Select(p => (uint)p * 0xFBA4C795 + nTweak).ToArray(); + bits = new BitArray(m) + { + Length = m + }; + Tweak = nTweak; } /// @@ -62,10 +65,12 @@ public BloomFilter(int m, int k, uint nTweak) public BloomFilter(int m, int k, uint nTweak, ReadOnlyMemory elements) { if (k < 0 || m < 0) throw new ArgumentOutOfRangeException(); - this.seeds = Enumerable.Range(0, k).Select(p => (uint)p * 0xFBA4C795 + nTweak).ToArray(); - this.bits = new BitArray(elements.ToArray()); - this.bits.Length = m; - this.Tweak = nTweak; + seeds = Enumerable.Range(0, k).Select(p => (uint)p * 0xFBA4C795 + nTweak).ToArray(); + bits = new BitArray(elements.ToArray()) + { + Length = m + }; + Tweak = nTweak; } /// diff --git a/src/Neo/Cryptography/Crypto.cs b/src/Neo/Cryptography/Crypto.cs index b96ee1cc3c..85ed0411a8 100644 --- a/src/Neo/Cryptography/Crypto.cs +++ b/src/Neo/Cryptography/Crypto.cs @@ -1,13 +1,18 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// Crypto.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.IO.Caching; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; using System; using System.Runtime.InteropServices; using System.Security.Cryptography; @@ -19,7 +24,21 @@ namespace Neo.Cryptography /// public static class Crypto { + private static readonly ECDsaCache CacheECDsa = new(); private static readonly bool IsOSX = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + private static readonly ECCurve secP256k1 = ECCurve.CreateFromFriendlyName("secP256k1"); + private static readonly X9ECParameters bouncySecp256k1 = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256k1"); + private static readonly X9ECParameters bouncySecp256r1 = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256r1"); + + /// + /// Holds domain parameters for Secp256r1 elliptic curve. + /// + private static readonly ECDomainParameters secp256r1DomainParams = new ECDomainParameters(bouncySecp256r1.Curve, bouncySecp256r1.G, bouncySecp256r1.N, bouncySecp256r1.H); + + /// + /// Holds domain parameters for Secp256k1 elliptic curve. + /// + private static readonly ECDomainParameters secp256k1DomainParams = new ECDomainParameters(bouncySecp256k1.Curve, bouncySecp256k1.G, bouncySecp256k1.N, bouncySecp256k1.H); /// /// Calculates the 160-bit hash value of the specified message. @@ -42,86 +61,147 @@ public static byte[] Hash256(ReadOnlySpan message) } /// - /// Signs the specified message using the ECDSA algorithm. + /// Signs the specified message using the ECDSA algorithm and specified hash algorithm. /// /// The message to be signed. - /// The private key to be used. - /// The public key to be used. + /// The private key to be used. + /// The curve of the signature, default is . + /// The hash algorithm to hash the message, default is SHA256. /// The ECDSA signature for the specified message. - public static byte[] Sign(byte[] message, byte[] prikey, byte[] pubkey) + public static byte[] Sign(byte[] message, byte[] priKey, ECC.ECCurve ecCurve = null, Hasher hasher = Hasher.SHA256) { + if (hasher == Hasher.Keccak256 || (IsOSX && ecCurve == ECC.ECCurve.Secp256k1)) + { + var domain = + ecCurve == null || ecCurve == ECC.ECCurve.Secp256r1 ? secp256r1DomainParams : + ecCurve == ECC.ECCurve.Secp256k1 ? secp256k1DomainParams : + throw new NotSupportedException(nameof(ecCurve)); + var signer = new Org.BouncyCastle.Crypto.Signers.ECDsaSigner(); + var privateKey = new BigInteger(1, priKey); + var priKeyParameters = new ECPrivateKeyParameters(privateKey, domain); + signer.Init(true, priKeyParameters); + var messageHash = + hasher == Hasher.SHA256 ? message.Sha256() : + hasher == Hasher.Keccak256 ? message.Keccak256() : + throw new NotSupportedException(nameof(hasher)); + var signature = signer.GenerateSignature(messageHash); + + var signatureBytes = new byte[64]; + var rBytes = signature[0].ToByteArrayUnsigned(); + var sBytes = signature[1].ToByteArrayUnsigned(); + + // Copy r and s into their respective parts of the signatureBytes array, aligning them to the right. + Buffer.BlockCopy(rBytes, 0, signatureBytes, 32 - rBytes.Length, rBytes.Length); + Buffer.BlockCopy(sBytes, 0, signatureBytes, 64 - sBytes.Length, sBytes.Length); + return signatureBytes; + } + + var curve = + ecCurve == null || ecCurve == ECC.ECCurve.Secp256r1 ? ECCurve.NamedCurves.nistP256 : + ecCurve == ECC.ECCurve.Secp256k1 ? secP256k1 : + throw new NotSupportedException(); + using var ecdsa = ECDsa.Create(new ECParameters { - Curve = ECCurve.NamedCurves.nistP256, - D = prikey, - Q = new ECPoint - { - X = pubkey[..32], - Y = pubkey[32..] - } + Curve = curve, + D = priKey, }); - return ecdsa.SignData(message, HashAlgorithmName.SHA256); + var hashAlg = + hasher == Hasher.SHA256 ? HashAlgorithmName.SHA256 : + throw new NotSupportedException(nameof(hasher)); + return ecdsa.SignData(message, hashAlg); } /// - /// Verifies that a digital signature is appropriate for the provided key and message. + /// Verifies that a digital signature is appropriate for the provided key, message and hash algorithm. /// /// The signed message. /// The signature to be verified. /// The public key to be used. + /// The hash algorithm to be used to hash the message, the default is SHA256. /// if the signature is valid; otherwise, . - public static bool VerifySignature(ReadOnlySpan message, ReadOnlySpan signature, ECC.ECPoint pubkey) + public static bool VerifySignature(ReadOnlySpan message, ReadOnlySpan signature, ECC.ECPoint pubkey, Hasher hasher = Hasher.SHA256) { if (signature.Length != 64) return false; - if (IsOSX && pubkey.Curve == ECC.ECCurve.Secp256k1) + if (hasher == Hasher.Keccak256 || (IsOSX && pubkey.Curve == ECC.ECCurve.Secp256k1)) { - var curve = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256k1"); - var domain = new Org.BouncyCastle.Crypto.Parameters.ECDomainParameters(curve.Curve, curve.G, curve.N, curve.H); - var point = curve.Curve.CreatePoint( - new Org.BouncyCastle.Math.BigInteger(pubkey.X.Value.ToString()), - new Org.BouncyCastle.Math.BigInteger(pubkey.Y.Value.ToString())); - var pubKey = new Org.BouncyCastle.Crypto.Parameters.ECPublicKeyParameters("ECDSA", point, domain); + var domain = + pubkey.Curve == ECC.ECCurve.Secp256r1 ? secp256r1DomainParams : + pubkey.Curve == ECC.ECCurve.Secp256k1 ? secp256k1DomainParams : + throw new NotSupportedException(nameof(pubkey.Curve)); + var curve = + pubkey.Curve == ECC.ECCurve.Secp256r1 ? bouncySecp256r1.Curve : + bouncySecp256k1.Curve; + + var point = curve.CreatePoint( + new BigInteger(pubkey.X.Value.ToString()), + new BigInteger(pubkey.Y.Value.ToString())); + var pubKey = new ECPublicKeyParameters("ECDSA", point, domain); var signer = new Org.BouncyCastle.Crypto.Signers.ECDsaSigner(); signer.Init(false, pubKey); var sig = signature.ToArray(); - var r = new Org.BouncyCastle.Math.BigInteger(1, sig, 0, 32); - var s = new Org.BouncyCastle.Math.BigInteger(1, sig, 32, 32); + var r = new BigInteger(1, sig, 0, 32); + var s = new BigInteger(1, sig, 32, 32); - return signer.VerifySignature(message.Sha256(), r, s); + var messageHash = + hasher == Hasher.SHA256 ? message.Sha256() : + hasher == Hasher.Keccak256 ? message.Keccak256() : + throw new NotSupportedException(nameof(hasher)); + + return signer.VerifySignature(messageHash, r, s); } - else + + var ecdsa = CreateECDsa(pubkey); + var hashAlg = + hasher == Hasher.SHA256 ? HashAlgorithmName.SHA256 : + throw new NotSupportedException(nameof(hasher)); + return ecdsa.VerifyData(message, signature, hashAlg); + } + + /// + /// Create and cache ECDsa objects + /// + /// + /// Cached ECDsa + /// + public static ECDsa CreateECDsa(ECC.ECPoint pubkey) + { + if (CacheECDsa.TryGet(pubkey, out var cache)) { - ECCurve curve = - pubkey.Curve == ECC.ECCurve.Secp256r1 ? ECCurve.NamedCurves.nistP256 : - pubkey.Curve == ECC.ECCurve.Secp256k1 ? ECCurve.CreateFromFriendlyName("secP256k1") : - throw new NotSupportedException(); - byte[] buffer = pubkey.EncodePoint(false); - using var ecdsa = ECDsa.Create(new ECParameters - { - Curve = curve, - Q = new ECPoint - { - X = buffer[1..33], - Y = buffer[33..] - } - }); - return ecdsa.VerifyData(message, signature, HashAlgorithmName.SHA256); + return cache.value; } + var curve = + pubkey.Curve == ECC.ECCurve.Secp256r1 ? ECCurve.NamedCurves.nistP256 : + pubkey.Curve == ECC.ECCurve.Secp256k1 ? secP256k1 : + throw new NotSupportedException(); + var buffer = pubkey.EncodePoint(false); + var ecdsa = ECDsa.Create(new ECParameters + { + Curve = curve, + Q = new ECPoint + { + X = buffer[1..33], + Y = buffer[33..] + } + }); + CacheECDsa.Add(new ECDsaCacheItem(pubkey, ecdsa)); + return ecdsa; } /// - /// Verifies that a digital signature is appropriate for the provided key and message. + /// Verifies that a digital signature is appropriate for the provided key, curve, message and hasher. /// /// The signed message. /// The signature to be verified. /// The public key to be used. /// The curve to be used by the ECDSA algorithm. + /// The hash algorithm to be used hash the message, the default is SHA256. /// if the signature is valid; otherwise, . - public static bool VerifySignature(ReadOnlySpan message, ReadOnlySpan signature, ReadOnlySpan pubkey, ECC.ECCurve curve) + public static bool VerifySignature(ReadOnlySpan message, ReadOnlySpan signature, ReadOnlySpan pubkey, ECC.ECCurve curve, Hasher hasher = Hasher.SHA256) { - return VerifySignature(message, signature, ECC.ECPoint.DecodePoint(pubkey, curve)); + return VerifySignature(message, signature, ECC.ECPoint.DecodePoint(pubkey, curve), hasher); } } } diff --git a/src/Neo/Cryptography/ECC/ECCurve.cs b/src/Neo/Cryptography/ECC/ECCurve.cs index 8b4416d108..def1ee0cb1 100644 --- a/src/Neo/Cryptography/ECC/ECCurve.cs +++ b/src/Neo/Cryptography/ECC/ECCurve.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ECCurve.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -36,11 +37,11 @@ public class ECCurve private ECCurve(BigInteger Q, BigInteger A, BigInteger B, BigInteger N, byte[] G) { this.Q = Q; - this.ExpectedECPointLength = ((int)Q.GetBitLength() + 7) / 8; + ExpectedECPointLength = ((int)VM.Utility.GetBitLength(Q) + 7) / 8; this.A = new ECFieldElement(A, this); this.B = new ECFieldElement(B, this); this.N = N; - this.Infinity = new ECPoint(null, null, this); + Infinity = new ECPoint(null, null, this); this.G = ECPoint.DecodePoint(G, this); } diff --git a/src/Neo/Cryptography/ECC/ECFieldElement.cs b/src/Neo/Cryptography/ECC/ECFieldElement.cs index 31bdb435e6..47d8bfa274 100644 --- a/src/Neo/Cryptography/ECC/ECFieldElement.cs +++ b/src/Neo/Cryptography/ECC/ECFieldElement.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ECFieldElement.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -24,7 +25,7 @@ public ECFieldElement(BigInteger value, ECCurve curve) throw new ArgumentNullException(nameof(curve)); if (value >= curve.Q) throw new ArgumentException("x value too large in field element"); - this.Value = value; + Value = value; this.curve = curve; } @@ -53,7 +54,7 @@ public bool Equals(ECFieldElement other) private static BigInteger[] FastLucasSequence(BigInteger p, BigInteger P, BigInteger Q, BigInteger k) { - int n = (int)k.GetBitLength(); + int n = (int)VM.Utility.GetBitLength(k); int s = k.GetLowestSetBit(); BigInteger Uh = 1; @@ -116,7 +117,7 @@ public ECFieldElement Sqrt() return null; BigInteger u = qMinusOne >> 2; BigInteger k = (u << 1) + 1; - BigInteger Q = this.Value; + BigInteger Q = Value; BigInteger fourQ = (Q << 2).Mod(curve.Q); BigInteger U, V; do @@ -125,7 +126,7 @@ public ECFieldElement Sqrt() BigInteger P; do { - P = rand.NextBigInteger((int)curve.Q.GetBitLength()); + P = rand.NextBigInteger((int)VM.Utility.GetBitLength(curve.Q)); } while (P >= curve.Q || BigInteger.ModPow(P * P - fourQ, legendreExponent, curve.Q) != qMinusOne); BigInteger[] result = FastLucasSequence(curve.Q, P, Q, k); diff --git a/src/Neo/Cryptography/ECC/ECPoint.cs b/src/Neo/Cryptography/ECC/ECPoint.cs index d49a0641da..3b7bdcc885 100644 --- a/src/Neo/Cryptography/ECC/ECPoint.cs +++ b/src/Neo/Cryptography/ECC/ECPoint.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ECPoint.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -48,9 +49,9 @@ internal ECPoint(ECFieldElement x, ECFieldElement y, ECCurve curve) { if ((x is null ^ y is null) || (curve is null)) throw new ArgumentException("Exactly one of the field elements is null"); - this.X = x; - this.Y = y; - this.Curve = curve; + X = x; + Y = y; + Curve = curve; } public int CompareTo(ECPoint other) @@ -201,6 +202,7 @@ public bool Equals(ECPoint other) { if (ReferenceEquals(this, other)) return true; if (other is null) return false; + if (!Curve.Equals(other.Curve)) return false; if (IsInfinity && other.IsInfinity) return true; if (IsInfinity || other.IsInfinity) return false; return X.Equals(other.X) && Y.Equals(other.Y); @@ -236,7 +238,7 @@ public override int GetHashCode() internal static ECPoint Multiply(ECPoint p, BigInteger k) { // floor(log2(k)) - int m = (int)k.GetBitLength(); + int m = (int)VM.Utility.GetBitLength(k); // width of the Window NAF sbyte width; @@ -376,21 +378,21 @@ public static bool TryParse(string value, ECCurve curve, out ECPoint point) internal ECPoint Twice() { - if (this.IsInfinity) + if (IsInfinity) return this; - if (this.Y.Value.Sign == 0) + if (Y.Value.Sign == 0) return Curve.Infinity; ECFieldElement TWO = new(2, Curve); ECFieldElement THREE = new(3, Curve); - ECFieldElement gamma = (this.X.Square() * THREE + Curve.A) / (Y * TWO); - ECFieldElement x3 = gamma.Square() - this.X * TWO; - ECFieldElement y3 = gamma * (this.X - x3) - this.Y; + ECFieldElement gamma = (X.Square() * THREE + Curve.A) / (Y * TWO); + ECFieldElement x3 = gamma.Square() - X * TWO; + ECFieldElement y3 = gamma * (X - x3) - Y; return new ECPoint(x3, y3, Curve); } private static sbyte[] WindowNaf(sbyte width, BigInteger k) { - sbyte[] wnaf = new sbyte[k.GetBitLength() + 1]; + sbyte[] wnaf = new sbyte[VM.Utility.GetBitLength(k) + 1]; short pow2wB = (short)(1 << width); int i = 0; int length = 0; diff --git a/src/Neo/Cryptography/Hasher.cs b/src/Neo/Cryptography/Hasher.cs new file mode 100644 index 0000000000..21c5fef6a8 --- /dev/null +++ b/src/Neo/Cryptography/Hasher.cs @@ -0,0 +1,29 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Hasher.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.Cryptography +{ + /// + /// Represents hash function identifiers supported by ECDSA message signature and verification. + /// + public enum Hasher : byte + { + /// + /// The SHA256 hash algorithm. + /// + SHA256 = 0x00, + + /// + /// The Keccak256 hash algorithm. + /// + Keccak256 = 0x01, + } +} diff --git a/src/Neo/Cryptography/Helper.cs b/src/Neo/Cryptography/Helper.cs index 6b78c665f8..f13fa08118 100644 --- a/src/Neo/Cryptography/Helper.cs +++ b/src/Neo/Cryptography/Helper.cs @@ -1,22 +1,25 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// Helper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.Wallets; +using Org.BouncyCastle.Crypto.Digests; using Org.BouncyCastle.Crypto.Engines; using Org.BouncyCastle.Crypto.Modes; using Org.BouncyCastle.Crypto.Parameters; using System; using System.Buffers.Binary; using System.Linq; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security.Cryptography; using static Neo.Helper; @@ -98,7 +101,7 @@ public static byte[] Murmur128(this byte[] value, uint seed) /// The computed hash code. public static byte[] Murmur128(this ReadOnlySpan value, uint seed) { - byte[] buffer = GC.AllocateUninitializedArray(16); + byte[] buffer = new byte[16]; using Murmur128 murmur = new(seed); murmur.TryComputeHash(value, buffer, out _); return buffer; @@ -151,6 +154,40 @@ public static byte[] Sha256(this Span value) return Sha256((ReadOnlySpan)value); } + /// + /// Computes the hash value for the specified byte array using the keccak256 algorithm. + /// + /// The input to compute the hash code for. + /// The computed hash code. + public static byte[] Keccak256(this byte[] value) + { + KeccakDigest keccak = new(256); + keccak.BlockUpdate(value, 0, value.Length); + byte[] result = new byte[keccak.GetDigestSize()]; + keccak.DoFinal(result, 0); + return result; + } + + /// + /// Computes the hash value for the specified byte array using the keccak256 algorithm. + /// + /// The input to compute the hash code for. + /// The computed hash code. + public static byte[] Keccak256(this ReadOnlySpan value) + { + return Keccak256(value.ToArray()); + } + + /// + /// Computes the hash value for the specified byte array using the keccak256 algorithm. + /// + /// The input to compute the hash code for. + /// The computed hash code. + public static byte[] Keccak256(this Span value) + { + return Keccak256(value.ToArray()); + } + public static byte[] AES256Encrypt(this byte[] plainData, byte[] key, byte[] nonce, byte[] associatedData = null) { if (nonce.Length != 12) throw new ArgumentOutOfRangeException(nameof(nonce)); @@ -158,7 +195,9 @@ public static byte[] AES256Encrypt(this byte[] plainData, byte[] key, byte[] non var cipherBytes = new byte[plainData.Length]; if (!IsOSX) { +#pragma warning disable SYSLIB0053 // Type or member is obsolete using var cipher = new AesGcm(key); +#pragma warning restore SYSLIB0053 // Type or member is obsolete cipher.Encrypt(nonce, plainData, cipherBytes, tag, associatedData); } else @@ -186,7 +225,9 @@ public static byte[] AES256Decrypt(this byte[] encryptedData, byte[] key, byte[] var decryptedData = new byte[cipherBytes.Length]; if (!IsOSX) { +#pragma warning disable SYSLIB0053 // Type or member is obsolete using var cipher = new AesGcm(key); +#pragma warning restore SYSLIB0053 // Type or member is obsolete cipher.Decrypt(nonce, cipherBytes, tag, decryptedData, associatedData); } else @@ -238,5 +279,29 @@ internal static bool Test(this BloomFilter filter, Transaction tx) return true; return false; } + + /// + /// Rotates the specified value left by the specified number of bits. + /// Similar in behavior to the x86 instruction ROL. + /// + /// The value to rotate. + /// The number of bits to rotate by. + /// Any value outside the range [0..31] is treated as congruent mod 32. + /// The rotated value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint RotateLeft(uint value, int offset) + => (value << offset) | (value >> (32 - offset)); + + /// + /// Rotates the specified value left by the specified number of bits. + /// Similar in behavior to the x86 instruction ROL. + /// + /// The value to rotate. + /// The number of bits to rotate by. + /// Any value outside the range [0..63] is treated as congruent mod 64. + /// The rotated value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong RotateLeft(ulong value, int offset) + => (value << offset) | (value >> (64 - offset)); } } diff --git a/src/Neo/Cryptography/MerkleTree.cs b/src/Neo/Cryptography/MerkleTree.cs index e813ec5e02..ed25171ef8 100644 --- a/src/Neo/Cryptography/MerkleTree.cs +++ b/src/Neo/Cryptography/MerkleTree.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// MerkleTree.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -31,12 +32,12 @@ public class MerkleTree internal MerkleTree(UInt256[] hashes) { - this.root = Build(hashes.Select(p => new MerkleTreeNode { Hash = p }).ToArray()); + root = Build(hashes.Select(p => new MerkleTreeNode { Hash = p }).ToArray()); if (root is null) return; int depth = 1; for (MerkleTreeNode i = root; i.LeftChild != null; i = i.LeftChild) depth++; - this.Depth = depth; + Depth = depth; } private static MerkleTreeNode Build(MerkleTreeNode[] leaves) diff --git a/src/Neo/Cryptography/MerkleTreeNode.cs b/src/Neo/Cryptography/MerkleTreeNode.cs index 189d0ef2ce..8fcc2dd5ce 100644 --- a/src/Neo/Cryptography/MerkleTreeNode.cs +++ b/src/Neo/Cryptography/MerkleTreeNode.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// MerkleTreeNode.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Cryptography/Murmur128.cs b/src/Neo/Cryptography/Murmur128.cs index 490b707814..15eca775cd 100644 --- a/src/Neo/Cryptography/Murmur128.cs +++ b/src/Neo/Cryptography/Murmur128.cs @@ -1,16 +1,16 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// Murmur128.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. using System; using System.Buffers.Binary; -using System.Numerics; using System.Runtime.CompilerServices; using System.Security.Cryptography; @@ -62,19 +62,19 @@ protected override void HashCore(ReadOnlySpan source) { ulong k1 = BinaryPrimitives.ReadUInt64LittleEndian(source[i..]); k1 *= c1; - k1 = BitOperations.RotateLeft(k1, r1); + k1 = Helper.RotateLeft(k1, r1); k1 *= c2; H1 ^= k1; - H1 = BitOperations.RotateLeft(H1, 27); + H1 = Helper.RotateLeft(H1, 27); H1 += H2; H1 = H1 * m + n1; ulong k2 = BinaryPrimitives.ReadUInt64LittleEndian(source[(i + 8)..]); k2 *= c2; - k2 = BitOperations.RotateLeft(k2, r2); + k2 = Helper.RotateLeft(k2, r2); k2 *= c1; H2 ^= k2; - H2 = BitOperations.RotateLeft(H2, 31); + H2 = Helper.RotateLeft(H2, 31); H2 += H1; H2 = H2 * m + n2; } @@ -101,14 +101,14 @@ protected override void HashCore(ReadOnlySpan source) case 1: remainingBytesL ^= (ulong)source[alignedLength] << 0; break; } - H2 ^= BitOperations.RotateLeft(remainingBytesH * c2, r2) * c1; - H1 ^= BitOperations.RotateLeft(remainingBytesL * c1, r1) * c2; + H2 ^= Helper.RotateLeft(remainingBytesH * c2, r2) * c1; + H1 ^= Helper.RotateLeft(remainingBytesL * c1, r1) * c2; } } protected override byte[] HashFinal() { - byte[] buffer = GC.AllocateUninitializedArray(sizeof(ulong) * 2); + byte[] buffer = new byte[sizeof(ulong) * 2]; TryHashFinal(buffer, out _); return buffer; } diff --git a/src/Neo/Cryptography/Murmur32.cs b/src/Neo/Cryptography/Murmur32.cs index c18d36847f..0f2c1b828d 100644 --- a/src/Neo/Cryptography/Murmur32.cs +++ b/src/Neo/Cryptography/Murmur32.cs @@ -1,16 +1,16 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// Murmur32.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. using System; using System.Buffers.Binary; -using System.Numerics; using System.Security.Cryptography; namespace Neo.Cryptography @@ -55,10 +55,10 @@ protected override void HashCore(ReadOnlySpan source) { uint k = BinaryPrimitives.ReadUInt32LittleEndian(source); k *= c1; - k = BitOperations.RotateLeft(k, r1); + k = Helper.RotateLeft(k, r1); k *= c2; hash ^= k; - hash = BitOperations.RotateLeft(hash, r2); + hash = Helper.RotateLeft(hash, r2); hash = hash * m + n; } if (source.Length > 0) @@ -71,7 +71,7 @@ protected override void HashCore(ReadOnlySpan source) case 1: remainingBytes ^= source[0]; break; } remainingBytes *= c1; - remainingBytes = BitOperations.RotateLeft(remainingBytes, r1); + remainingBytes = Helper.RotateLeft(remainingBytes, r1); remainingBytes *= c2; hash ^= remainingBytes; } @@ -79,7 +79,7 @@ protected override void HashCore(ReadOnlySpan source) protected override byte[] HashFinal() { - byte[] buffer = GC.AllocateUninitializedArray(sizeof(uint)); + byte[] buffer = new byte[sizeof(uint)]; TryHashFinal(buffer, out _); return buffer; } diff --git a/src/Neo/Cryptography/RIPEMD160Managed.cs b/src/Neo/Cryptography/RIPEMD160Managed.cs index 0b61ac295e..932c1cfea6 100644 --- a/src/Neo/Cryptography/RIPEMD160Managed.cs +++ b/src/Neo/Cryptography/RIPEMD160Managed.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// RIPEMD160Managed.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Hardfork.cs b/src/Neo/Hardfork.cs index 5a9aef6b5c..7bd3cc0aef 100644 --- a/src/Neo/Hardfork.cs +++ b/src/Neo/Hardfork.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// Hardfork.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -13,6 +14,8 @@ namespace Neo public enum Hardfork : byte { HF_Aspidochelone, - HF_Basilisk + HF_Basilisk, + HF_Cockatrice, + HF_Domovoi } } diff --git a/src/Neo/Helper.cs b/src/Neo/Helper.cs index 49e4e8efd4..00099bc148 100644 --- a/src/Neo/Helper.cs +++ b/src/Neo/Helper.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// Helper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/IO/Actors/Idle.cs b/src/Neo/IO/Actors/Idle.cs index 8292bb9779..e722d057ee 100644 --- a/src/Neo/IO/Actors/Idle.cs +++ b/src/Neo/IO/Actors/Idle.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// Idle.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/IO/Actors/PriorityMailbox.cs b/src/Neo/IO/Actors/PriorityMailbox.cs index a5fa0bd2b0..0b08c04d70 100644 --- a/src/Neo/IO/Actors/PriorityMailbox.cs +++ b/src/Neo/IO/Actors/PriorityMailbox.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// PriorityMailbox.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/IO/Actors/PriorityMessageQueue.cs b/src/Neo/IO/Actors/PriorityMessageQueue.cs index 81aba26cd0..225720ec97 100644 --- a/src/Neo/IO/Actors/PriorityMessageQueue.cs +++ b/src/Neo/IO/Actors/PriorityMessageQueue.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// PriorityMessageQueue.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/IO/ByteArrayComparer.cs b/src/Neo/IO/ByteArrayComparer.cs deleted file mode 100644 index 13d8c30bd1..0000000000 --- a/src/Neo/IO/ByteArrayComparer.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php -// for more details. -// -// Redistribution and use in source and binary forms with or without -// modifications are permitted. - -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; - -namespace Neo.IO -{ - internal class ByteArrayComparer : IComparer - { - public static readonly ByteArrayComparer Default = new(1); - public static readonly ByteArrayComparer Reverse = new(-1); - - private readonly int direction; - - private ByteArrayComparer(int direction) - { - this.direction = direction; - } - - public int Compare(byte[] x, byte[] y) - { - return direction > 0 - ? CompareInternal(x, y) - : -CompareInternal(x, y); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int CompareInternal(byte[] x, byte[] y) - { - int length = Math.Min(x.Length, y.Length); - for (int i = 0; i < length; i++) - { - int r = x[i].CompareTo(y[i]); - if (r != 0) return r; - } - return x.Length.CompareTo(y.Length); - } - } -} diff --git a/src/Neo/IO/Caching/Cache.cs b/src/Neo/IO/Caching/Cache.cs index f0c52774b9..7895a8ca2d 100644 --- a/src/Neo/IO/Caching/Cache.cs +++ b/src/Neo/IO/Caching/Cache.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// Cache.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -26,9 +27,9 @@ protected class CacheItem public CacheItem(TKey key, TValue value) { - this.Key = key; - this.Value = value; - this.Time = TimeProvider.Current.UtcNow; + Key = key; + Value = value; + Time = TimeProvider.Current.UtcNow; } } @@ -75,7 +76,7 @@ public int Count public Cache(int max_capacity, IEqualityComparer comparer = null) { this.max_capacity = max_capacity; - this.InnerDictionary = new Dictionary(comparer); + InnerDictionary = new Dictionary(comparer); } public void Add(TValue item) diff --git a/src/Neo/IO/Caching/ECDsaCache.cs b/src/Neo/IO/Caching/ECDsaCache.cs new file mode 100644 index 0000000000..b25f29da8e --- /dev/null +++ b/src/Neo/IO/Caching/ECDsaCache.cs @@ -0,0 +1,29 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ECDsaCache.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Collections.Generic; +using System.Security.Cryptography; + +namespace Neo.IO.Caching +{ + record ECDsaCacheItem(Cryptography.ECC.ECPoint key, ECDsa value); + internal class ECDsaCache : FIFOCache + { + public ECDsaCache(int max_capacity = 20000) : base(max_capacity, EqualityComparer.Default) + { + } + + protected override Cryptography.ECC.ECPoint GetKeyForItem(ECDsaCacheItem item) + { + return item.key; + } + } +} diff --git a/src/Neo/IO/Caching/ECPointCache.cs b/src/Neo/IO/Caching/ECPointCache.cs index 01e7a1ad16..a32671d30f 100644 --- a/src/Neo/IO/Caching/ECPointCache.cs +++ b/src/Neo/IO/Caching/ECPointCache.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ECPointCache.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/IO/Caching/FIFOCache.cs b/src/Neo/IO/Caching/FIFOCache.cs index 755afb400a..af3e5e469d 100644 --- a/src/Neo/IO/Caching/FIFOCache.cs +++ b/src/Neo/IO/Caching/FIFOCache.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// FIFOCache.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/IO/Caching/HashSetCache.cs b/src/Neo/IO/Caching/HashSetCache.cs index 89bef03d2a..bdef1c5e3a 100644 --- a/src/Neo/IO/Caching/HashSetCache.cs +++ b/src/Neo/IO/Caching/HashSetCache.cs @@ -1,8 +1,9 @@ -// Copyright (C) 2015-2022 The Neo Project. +// Copyright (C) 2015-2024 The Neo Project. // -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// HashSetCache.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. // // Redistribution and use in source and binary forms with or without @@ -41,7 +42,7 @@ public HashSetCache(int bucketCapacity, int maxBucketCount = 10) if (bucketCapacity <= 0) throw new ArgumentOutOfRangeException($"{nameof(bucketCapacity)} should be greater than 0"); if (maxBucketCount <= 0) throw new ArgumentOutOfRangeException($"{nameof(maxBucketCount)} should be greater than 0"); - this.Count = 0; + Count = 0; this.bucketCapacity = bucketCapacity; this.maxBucketCount = maxBucketCount; sets.AddFirst(new HashSet()); diff --git a/src/Neo/IO/Caching/IndexedQueue.cs b/src/Neo/IO/Caching/IndexedQueue.cs index 0db5f98ed9..54440a871b 100644 --- a/src/Neo/IO/Caching/IndexedQueue.cs +++ b/src/Neo/IO/Caching/IndexedQueue.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// IndexedQueue.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/IO/Caching/KeyedCollectionSlim.cs b/src/Neo/IO/Caching/KeyedCollectionSlim.cs index 935faf36b2..a24dd87e4c 100644 --- a/src/Neo/IO/Caching/KeyedCollectionSlim.cs +++ b/src/Neo/IO/Caching/KeyedCollectionSlim.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// KeyedCollectionSlim.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using System; using System.Collections.Generic; diff --git a/src/Neo/IO/Caching/ReflectionCache.cs b/src/Neo/IO/Caching/ReflectionCache.cs index 590efc5c36..2fd8f5fceb 100644 --- a/src/Neo/IO/Caching/ReflectionCache.cs +++ b/src/Neo/IO/Caching/ReflectionCache.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ReflectionCache.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/IO/Caching/ReflectionCacheAttribute.cs b/src/Neo/IO/Caching/ReflectionCacheAttribute.cs index 6281d9a52d..20aeb91320 100644 --- a/src/Neo/IO/Caching/ReflectionCacheAttribute.cs +++ b/src/Neo/IO/Caching/ReflectionCacheAttribute.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ReflectionCacheAttribute.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/IO/Caching/RelayCache.cs b/src/Neo/IO/Caching/RelayCache.cs index 4442f17bd6..0f617f4d24 100644 --- a/src/Neo/IO/Caching/RelayCache.cs +++ b/src/Neo/IO/Caching/RelayCache.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// RelayCache.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/IO/Helper.cs b/src/Neo/IO/Helper.cs index adaf127081..2e6d362ee1 100644 --- a/src/Neo/IO/Helper.cs +++ b/src/Neo/IO/Helper.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// Helper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -101,7 +102,7 @@ public static ISerializable AsSerializable(this ReadOnlyMemory value, Type public static ReadOnlyMemory CompressLz4(this ReadOnlySpan data) { int maxLength = LZ4Codec.MaximumOutputSize(data.Length); - byte[] buffer = GC.AllocateUninitializedArray(sizeof(uint) + maxLength); + byte[] buffer = new byte[sizeof(uint) + maxLength]; BinaryPrimitives.WriteInt32LittleEndian(buffer, data.Length); int length = LZ4Codec.Encode(data, buffer.AsSpan(sizeof(uint))); return buffer.AsMemory(0, sizeof(uint) + length); @@ -117,7 +118,7 @@ public static byte[] DecompressLz4(this ReadOnlySpan data, int maxOutput) { int length = BinaryPrimitives.ReadInt32LittleEndian(data); if (length < 0 || length > maxOutput) throw new FormatException(); - byte[] result = GC.AllocateUninitializedArray(length); + byte[] result = new byte[length]; if (LZ4Codec.Decode(data[4..], result) != length) throw new FormatException(); return result; diff --git a/src/Neo/Ledger/Blockchain.ApplicationExecuted.cs b/src/Neo/Ledger/Blockchain.ApplicationExecuted.cs index c2bb6cf5b5..e1fa5fae62 100644 --- a/src/Neo/Ledger/Blockchain.ApplicationExecuted.cs +++ b/src/Neo/Ledger/Blockchain.ApplicationExecuted.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// Blockchain.ApplicationExecuted.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -61,7 +62,7 @@ internal ApplicationExecuted(ApplicationEngine engine) Transaction = engine.ScriptContainer as Transaction; Trigger = engine.Trigger; VMState = engine.State; - GasConsumed = engine.GasConsumed; + GasConsumed = engine.FeeConsumed; Exception = engine.FaultException; Stack = engine.ResultStack.ToArray(); Notifications = engine.Notifications.ToArray(); diff --git a/src/Neo/Ledger/Blockchain.cs b/src/Neo/Ledger/Blockchain.cs index 8acec8c974..ff9c8e29d8 100644 --- a/src/Neo/Ledger/Blockchain.cs +++ b/src/Neo/Ledger/Blockchain.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// Blockchain.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -206,7 +207,7 @@ private void OnFillMemoryPool(IEnumerable transactions) { if (NativeContract.Ledger.ContainsTransaction(snapshot, tx.Hash)) continue; - if (NativeContract.Ledger.ContainsConflictHash(snapshot, tx.Hash, tx.Signers.Select(s => s.Account))) + if (NativeContract.Ledger.ContainsConflictHash(snapshot, tx.Hash, tx.Signers.Select(s => s.Account), system.Settings.MaxTraceableBlocks)) continue; // First remove the tx if it is unverified in the pool. system.MemPool.TryRemoveUnVerified(tx.Hash, out _); @@ -338,7 +339,11 @@ private VerifyResult OnNewExtensiblePayload(ExtensiblePayload payload) private VerifyResult OnNewTransaction(Transaction transaction) { - if (system.ContainsTransaction(transaction.Hash)) return VerifyResult.AlreadyExists; + switch (system.ContainsTransaction(transaction.Hash)) + { + case ContainsTransactionType.ExistsInPool: return VerifyResult.AlreadyInPool; + case ContainsTransactionType.ExistsInLedger: return VerifyResult.AlreadyExists; + } if (system.ContainsConflictHash(transaction.Hash, transaction.Signers.Select(s => s.Account))) return VerifyResult.HasConflicts; return system.MemPool.TryAdd(transaction, system.StoreView); } @@ -392,11 +397,22 @@ protected override void OnReceive(object message) private void OnTransaction(Transaction tx) { - if (system.ContainsTransaction(tx.Hash)) - SendRelayResult(tx, VerifyResult.AlreadyExists); - else if (system.ContainsConflictHash(tx.Hash, tx.Signers.Select(s => s.Account))) - SendRelayResult(tx, VerifyResult.HasConflicts); - else system.TxRouter.Forward(new TransactionRouter.Preverify(tx, true)); + switch (system.ContainsTransaction(tx.Hash)) + { + case ContainsTransactionType.ExistsInPool: + SendRelayResult(tx, VerifyResult.AlreadyInPool); + break; + case ContainsTransactionType.ExistsInLedger: + SendRelayResult(tx, VerifyResult.AlreadyExists); + break; + default: + { + if (system.ContainsConflictHash(tx.Hash, tx.Signers.Select(s => s.Account))) + SendRelayResult(tx, VerifyResult.HasConflicts); + else system.TxRouter.Forward(new TransactionRouter.Preverify(tx, true)); + break; + } + } } private void Persist(Block block) @@ -408,7 +424,12 @@ private void Persist(Block block) using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.OnPersist, null, snapshot, block, system.Settings, 0)) { engine.LoadScript(onPersistScript); - if (engine.Execute() != VMState.HALT) throw new InvalidOperationException(); + if (engine.Execute() != VMState.HALT) + { + if (engine.FaultException != null) + throw engine.FaultException; + throw new InvalidOperationException(); + } ApplicationExecuted application_executed = new(engine); Context.System.EventStream.Publish(application_executed); all_application_executed.Add(application_executed); @@ -437,7 +458,12 @@ private void Persist(Block block) using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.PostPersist, null, snapshot, block, system.Settings, 0)) { engine.LoadScript(postPersistScript); - if (engine.Execute() != VMState.HALT) throw new InvalidOperationException(); + if (engine.Execute() != VMState.HALT) + { + if (engine.FaultException != null) + throw engine.FaultException; + throw new InvalidOperationException(); + } ApplicationExecuted application_executed = new(engine); Context.System.EventStream.Publish(application_executed); all_application_executed.Add(application_executed); diff --git a/src/Neo/Ledger/HeaderCache.cs b/src/Neo/Ledger/HeaderCache.cs index 4286fa2b3a..ac2208011f 100644 --- a/src/Neo/Ledger/HeaderCache.cs +++ b/src/Neo/Ledger/HeaderCache.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// HeaderCache.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Ledger/MemoryPool.cs b/src/Neo/Ledger/MemoryPool.cs index a966835fc3..23eb711e87 100644 --- a/src/Neo/Ledger/MemoryPool.cs +++ b/src/Neo/Ledger/MemoryPool.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// MemoryPool.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -292,7 +293,7 @@ internal VerifyResult TryAdd(Transaction tx, DataCache snapshot) { var poolItem = new PoolItem(tx); - if (_unsortedTransactions.ContainsKey(tx.Hash)) return VerifyResult.AlreadyExists; + if (_unsortedTransactions.ContainsKey(tx.Hash)) return VerifyResult.AlreadyInPool; List removedTransactions = null; _txRwLock.EnterWriteLock(); diff --git a/src/Neo/Ledger/PoolItem.cs b/src/Neo/Ledger/PoolItem.cs index 2fc7eda0b9..f8451c1cd9 100644 --- a/src/Neo/Ledger/PoolItem.cs +++ b/src/Neo/Ledger/PoolItem.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// PoolItem.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Ledger/TransactionRemovalReason.cs b/src/Neo/Ledger/TransactionRemovalReason.cs index fa66b60981..42e11b3f75 100644 --- a/src/Neo/Ledger/TransactionRemovalReason.cs +++ b/src/Neo/Ledger/TransactionRemovalReason.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// TransactionRemovalReason.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Ledger/TransactionRemovedEventArgs.cs b/src/Neo/Ledger/TransactionRemovedEventArgs.cs index de35dd115e..403b24a195 100644 --- a/src/Neo/Ledger/TransactionRemovedEventArgs.cs +++ b/src/Neo/Ledger/TransactionRemovedEventArgs.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// TransactionRemovedEventArgs.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Ledger/TransactionRouter.cs b/src/Neo/Ledger/TransactionRouter.cs index 442cf91ccb..9cc0ed8a9e 100644 --- a/src/Neo/Ledger/TransactionRouter.cs +++ b/src/Neo/Ledger/TransactionRouter.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// TransactionRouter.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Ledger/TransactionVerificationContext.cs b/src/Neo/Ledger/TransactionVerificationContext.cs index 5b6cc6cc9c..2300c6da30 100644 --- a/src/Neo/Ledger/TransactionVerificationContext.cs +++ b/src/Neo/Ledger/TransactionVerificationContext.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// TransactionVerificationContext.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Ledger/VerifyResult.cs b/src/Neo/Ledger/VerifyResult.cs index 94c9e78bf8..7a242b4c60 100644 --- a/src/Neo/Ledger/VerifyResult.cs +++ b/src/Neo/Ledger/VerifyResult.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// VerifyResult.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -27,6 +28,11 @@ public enum VerifyResult : byte /// AlreadyExists, + /// + /// Indicates that an with the same hash already exists in the memory pool. + /// + AlreadyInPool, + /// /// Indicates that the is full and the transaction cannot be verified. /// diff --git a/src/Neo/Neo.csproj b/src/Neo/Neo.csproj index c6919a3371..835224eddd 100644 --- a/src/Neo/Neo.csproj +++ b/src/Neo/Neo.csproj @@ -1,24 +1,35 @@ + netstandard2.1;net8.0 true + Neo NEO;AntShares;Blockchain;Smart Contract + ../../bin/$(PackageId) - + + + + + + + - - - - - + + + + + - + + + diff --git a/src/Neo/NeoSystem.cs b/src/Neo/NeoSystem.cs index 2f66389672..b752f16712 100644 --- a/src/Neo/NeoSystem.cs +++ b/src/Neo/NeoSystem.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// NeoSystem.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -96,8 +97,8 @@ public class NeoSystem : IDisposable internal RelayCache RelayCache { get; } = new(100); private ImmutableList services = ImmutableList.Empty; - private readonly string storage_engine; private readonly IStore store; + private readonly IStoreProvider storageProvider; private ChannelsConfig start_message = null; private int suspend = 0; @@ -113,19 +114,31 @@ static NeoSystem() /// Initializes a new instance of the class. /// /// The protocol settings of the . - /// The storage engine used to create the objects. If this parameter is , a default in-memory storage engine will be used. - /// The path of the storage. If is the default in-memory storage engine, this parameter is ignored. - public NeoSystem(ProtocolSettings settings, string storageEngine = null, string storagePath = null) + /// The storage engine used to create the objects. If this parameter is , a default in-memory storage engine will be used. + /// The path of the storage. If is the default in-memory storage engine, this parameter is ignored. + public NeoSystem(ProtocolSettings settings, string storageProvider = null, string storagePath = null) : + this(settings, StoreFactory.GetStoreProvider(storageProvider ?? nameof(MemoryStore)) + ?? throw new ArgumentException($"Can't find the storage provider {storageProvider}", nameof(storageProvider)), storagePath) { - this.Settings = settings; - this.GenesisBlock = CreateGenesisBlock(settings); - this.storage_engine = storageEngine ?? nameof(MemoryStore); - this.store = LoadStore(storagePath); - this.MemPool = new MemoryPool(this); - this.Blockchain = ActorSystem.ActorOf(Ledger.Blockchain.Props(this)); - this.LocalNode = ActorSystem.ActorOf(Network.P2P.LocalNode.Props(this)); - this.TaskManager = ActorSystem.ActorOf(Network.P2P.TaskManager.Props(this)); - this.TxRouter = ActorSystem.ActorOf(TransactionRouter.Props(this)); + } + + /// + /// Initializes a new instance of the class. + /// + /// The protocol settings of the . + /// The to use. + /// The path of the storage. If is the default in-memory storage engine, this parameter is ignored. + public NeoSystem(ProtocolSettings settings, IStoreProvider storageProvider, string storagePath = null) + { + Settings = settings; + GenesisBlock = CreateGenesisBlock(settings); + this.storageProvider = storageProvider; + store = storageProvider.GetStore(storagePath); + MemPool = new MemoryPool(this); + Blockchain = ActorSystem.ActorOf(Ledger.Blockchain.Props(this)); + LocalNode = ActorSystem.ActorOf(Network.P2P.LocalNode.Props(this)); + TaskManager = ActorSystem.ActorOf(Network.P2P.TaskManager.Props(this)); + TxRouter = ActorSystem.ActorOf(TransactionRouter.Props(this)); foreach (var plugin in Plugin.Plugins) plugin.OnSystemLoaded(this); Blockchain.Ask(new Blockchain.Initialize()).Wait(); @@ -217,7 +230,7 @@ public void EnsureStopped(IActorRef actor) /// The loaded . public IStore LoadStore(string path) { - return StoreFactory.GetStore(storage_engine, path); + return storageProvider.GetStore(path); } /// @@ -273,10 +286,11 @@ public SnapshotCache GetSnapshot() /// /// The hash of the transaction /// if the transaction exists; otherwise, . - public bool ContainsTransaction(UInt256 hash) + public ContainsTransactionType ContainsTransaction(UInt256 hash) { - if (MemPool.ContainsKey(hash)) return true; - return NativeContract.Ledger.ContainsTransaction(StoreView, hash); + if (MemPool.ContainsKey(hash)) return ContainsTransactionType.ExistsInPool; + return NativeContract.Ledger.ContainsTransaction(StoreView, hash) ? + ContainsTransactionType.ExistsInLedger : ContainsTransactionType.NotExist; } /// @@ -287,7 +301,7 @@ public bool ContainsTransaction(UInt256 hash) /// if the transaction conflicts with on-chain transaction; otherwise, . public bool ContainsConflictHash(UInt256 hash, IEnumerable signers) { - return NativeContract.Ledger.ContainsConflictHash(StoreView, hash, signers); + return NativeContract.Ledger.ContainsConflictHash(StoreView, hash, signers, Settings.MaxTraceableBlocks); } } } diff --git a/src/Neo/Network/P2P/Capabilities/FullNodeCapability.cs b/src/Neo/Network/P2P/Capabilities/FullNodeCapability.cs index 6e29a05f0b..52bc765d7c 100644 --- a/src/Neo/Network/P2P/Capabilities/FullNodeCapability.cs +++ b/src/Neo/Network/P2P/Capabilities/FullNodeCapability.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// FullNodeCapability.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Network/P2P/Capabilities/NodeCapability.cs b/src/Neo/Network/P2P/Capabilities/NodeCapability.cs index 880ec09750..c47f238a4f 100644 --- a/src/Neo/Network/P2P/Capabilities/NodeCapability.cs +++ b/src/Neo/Network/P2P/Capabilities/NodeCapability.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// NodeCapability.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -32,7 +33,7 @@ public abstract class NodeCapability : ISerializable /// The type of the . protected NodeCapability(NodeCapabilityType type) { - this.Type = type; + Type = type; } void ISerializable.Deserialize(ref MemoryReader reader) @@ -55,7 +56,9 @@ public static NodeCapability DeserializeFrom(ref MemoryReader reader) NodeCapabilityType type = (NodeCapabilityType)reader.ReadByte(); NodeCapability capability = type switch { +#pragma warning disable CS0612 // Type or member is obsolete NodeCapabilityType.TcpServer or NodeCapabilityType.WsServer => new ServerCapability(type), +#pragma warning restore CS0612 // Type or member is obsolete NodeCapabilityType.FullNode => new FullNodeCapability(), _ => throw new FormatException(), }; diff --git a/src/Neo/Network/P2P/Capabilities/NodeCapabilityType.cs b/src/Neo/Network/P2P/Capabilities/NodeCapabilityType.cs index f89bb2e6f3..419d086fa9 100644 --- a/src/Neo/Network/P2P/Capabilities/NodeCapabilityType.cs +++ b/src/Neo/Network/P2P/Capabilities/NodeCapabilityType.cs @@ -1,13 +1,16 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// NodeCapabilityType.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. +using System; + namespace Neo.Network.P2P.Capabilities { /// @@ -25,6 +28,7 @@ public enum NodeCapabilityType : byte /// /// Indicates that the node is listening on a WebSocket port. /// + [Obsolete] WsServer = 0x02, #endregion diff --git a/src/Neo/Network/P2P/Capabilities/ServerCapability.cs b/src/Neo/Network/P2P/Capabilities/ServerCapability.cs index a4f4a0ebf0..e8c2d110df 100644 --- a/src/Neo/Network/P2P/Capabilities/ServerCapability.cs +++ b/src/Neo/Network/P2P/Capabilities/ServerCapability.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ServerCapability.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -35,7 +36,9 @@ public class ServerCapability : NodeCapability /// The port that the node is listening on. public ServerCapability(NodeCapabilityType type, ushort port = 0) : base(type) { +#pragma warning disable CS0612 // Type or member is obsolete if (type != NodeCapabilityType.TcpServer && type != NodeCapabilityType.WsServer) +#pragma warning restore CS0612 // Type or member is obsolete { throw new ArgumentException(nameof(type)); } diff --git a/src/Neo/Network/P2P/ChannelsConfig.cs b/src/Neo/Network/P2P/ChannelsConfig.cs index a8d3861024..cc64242066 100644 --- a/src/Neo/Network/P2P/ChannelsConfig.cs +++ b/src/Neo/Network/P2P/ChannelsConfig.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ChannelsConfig.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -22,11 +23,6 @@ public class ChannelsConfig /// public IPEndPoint Tcp { get; set; } - /// - /// Web socket configuration. - /// - public IPEndPoint WebSocket { get; set; } - /// /// Minimum desired connections. /// diff --git a/src/Neo/Network/P2P/Connection.cs b/src/Neo/Network/P2P/Connection.cs index 9633879ead..66d12183f0 100644 --- a/src/Neo/Network/P2P/Connection.cs +++ b/src/Neo/Network/P2P/Connection.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// Connection.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -12,8 +13,6 @@ using Akka.IO; using System; using System.Net; -using System.Net.WebSockets; -using System.Threading; namespace Neo.Network.P2P { @@ -47,7 +46,6 @@ internal class Close { public bool Abort; } private ICancelable timer; private readonly IActorRef tcp; - private readonly WebSocket ws; private bool disconnected = false; /// @@ -58,41 +56,17 @@ internal class Close { public bool Abort; } /// The address of the local node. protected Connection(object connection, IPEndPoint remote, IPEndPoint local) { - this.Remote = remote; - this.Local = local; - this.timer = Context.System.Scheduler.ScheduleTellOnceCancelable(TimeSpan.FromSeconds(connectionTimeoutLimitStart), Self, new Close { Abort = true }, ActorRefs.NoSender); + Remote = remote; + Local = local; + timer = Context.System.Scheduler.ScheduleTellOnceCancelable(TimeSpan.FromSeconds(connectionTimeoutLimitStart), Self, new Close { Abort = true }, ActorRefs.NoSender); switch (connection) { case IActorRef tcp: this.tcp = tcp; break; - case WebSocket ws: - this.ws = ws; - WsReceive(); - break; } } - private void WsReceive() - { - byte[] buffer = new byte[512]; - ws.ReceiveAsync(buffer, CancellationToken.None).PipeTo(Self, - success: p => - { - switch (p.MessageType) - { - case WebSocketMessageType.Binary: - return new Tcp.Received(ByteString.FromBytes(buffer, 0, p.Count)); - case WebSocketMessageType.Close: - return Tcp.PeerClosed.Instance; - default: - ws.Abort(); - return Tcp.Aborted.Instance; - } - }, - failure: ex => new Tcp.ErrorClosed(ex.Message)); - } - /// /// Disconnect from the remote node. /// @@ -104,10 +78,6 @@ public void Disconnect(bool abort = false) { tcp.Tell(abort ? Tcp.Abort.Instance : Tcp.Close.Instance); } - else - { - ws.Abort(); - } Context.Stop(Self); } @@ -162,7 +132,6 @@ protected override void PostStop() if (!disconnected) tcp?.Tell(Tcp.Close.Instance); timer.CancelIfNotNull(); - ws?.Dispose(); base.PostStop(); } @@ -176,13 +145,6 @@ protected void SendData(ByteString data) { tcp.Tell(Tcp.Write.Create(data, Ack.Instance)); } - else - { - ArraySegment segment = new(data.ToArray()); - ws.SendAsync(segment, WebSocketMessageType.Binary, true, CancellationToken.None).PipeTo(Self, - success: () => Ack.Instance, - failure: ex => new Tcp.ErrorClosed(ex.Message)); - } } } } diff --git a/src/Neo/Network/P2P/Helper.cs b/src/Neo/Network/P2P/Helper.cs index 1ac9a5d245..f171a7b0a3 100644 --- a/src/Neo/Network/P2P/Helper.cs +++ b/src/Neo/Network/P2P/Helper.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// Helper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Network/P2P/LocalNode.cs b/src/Neo/Network/P2P/LocalNode.cs index 5c980a957f..78a967047c 100644 --- a/src/Neo/Network/P2P/LocalNode.cs +++ b/src/Neo/Network/P2P/LocalNode.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// LocalNode.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -87,7 +88,7 @@ static LocalNode() public LocalNode(NeoSystem system) { this.system = system; - this.SeedList = new IPEndPoint[system.Settings.SeedList.Length]; + SeedList = new IPEndPoint[system.Settings.SeedList.Length]; // Start dns resolution in parallel string[] seedList = system.Settings.SeedList; diff --git a/src/Neo/Network/P2P/Message.cs b/src/Neo/Network/P2P/Message.cs index 00242c5fec..9d3c63a85b 100644 --- a/src/Neo/Network/P2P/Message.cs +++ b/src/Neo/Network/P2P/Message.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// Message.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Network/P2P/MessageCommand.cs b/src/Neo/Network/P2P/MessageCommand.cs index 1205903f2c..e0a5444dd5 100644 --- a/src/Neo/Network/P2P/MessageCommand.cs +++ b/src/Neo/Network/P2P/MessageCommand.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// MessageCommand.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Network/P2P/MessageFlags.cs b/src/Neo/Network/P2P/MessageFlags.cs index 63413bee22..69466c9606 100644 --- a/src/Neo/Network/P2P/MessageFlags.cs +++ b/src/Neo/Network/P2P/MessageFlags.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// MessageFlags.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Network/P2P/Payloads/AddrPayload.cs b/src/Neo/Network/P2P/Payloads/AddrPayload.cs index f038a01a8a..ebdceb65ec 100644 --- a/src/Neo/Network/P2P/Payloads/AddrPayload.cs +++ b/src/Neo/Network/P2P/Payloads/AddrPayload.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// AddrPayload.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Network/P2P/Payloads/Block.cs b/src/Neo/Network/P2P/Payloads/Block.cs index 955bf98bf6..3c727b9886 100644 --- a/src/Neo/Network/P2P/Payloads/Block.cs +++ b/src/Neo/Network/P2P/Payloads/Block.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// Block.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Network/P2P/Payloads/Conditions/AndCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/AndCondition.cs index 096b47622b..e395b10d5d 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/AndCondition.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/AndCondition.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// AndCondition.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -49,9 +50,13 @@ protected override void SerializeWithoutType(BinaryWriter writer) writer.Write(Expressions); } - private protected override void ParseJson(JObject json) + private protected override void ParseJson(JObject json, int maxNestDepth) { - Expressions = ((JArray)json["expressions"]).Select(p => FromJson((JObject)p)).ToArray(); + if (maxNestDepth <= 0) throw new FormatException(); + JArray expressions = (JArray)json["expressions"]; + if (expressions.Count > MaxSubitems) throw new FormatException(); + Expressions = expressions.Select(p => FromJson((JObject)p, maxNestDepth - 1)).ToArray(); + if (Expressions.Length == 0) throw new FormatException(); } public override JObject ToJson() diff --git a/src/Neo/Network/P2P/Payloads/Conditions/BooleanCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/BooleanCondition.cs index ad6a914dd1..e7609fcbaf 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/BooleanCondition.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/BooleanCondition.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// BooleanCondition.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -42,7 +43,7 @@ protected override void SerializeWithoutType(BinaryWriter writer) writer.Write(Expression); } - private protected override void ParseJson(JObject json) + private protected override void ParseJson(JObject json, int maxNestDepth) { Expression = json["expression"].GetBoolean(); } @@ -56,7 +57,7 @@ public override JObject ToJson() public override StackItem ToStackItem(ReferenceCounter referenceCounter) { - var result = (Array)base.ToStackItem(referenceCounter); + var result = (VM.Types.Array)base.ToStackItem(referenceCounter); result.Add(Expression); return result; } diff --git a/src/Neo/Network/P2P/Payloads/Conditions/CalledByContractCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/CalledByContractCondition.cs index 82f24c7676..f853743b72 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/CalledByContractCondition.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/CalledByContractCondition.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// CalledByContractCondition.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -42,7 +43,7 @@ protected override void SerializeWithoutType(BinaryWriter writer) writer.Write(Hash); } - private protected override void ParseJson(JObject json) + private protected override void ParseJson(JObject json, int maxNestDepth) { Hash = UInt160.Parse(json["hash"].GetString()); } @@ -56,7 +57,7 @@ public override JObject ToJson() public override StackItem ToStackItem(ReferenceCounter referenceCounter) { - var result = (Array)base.ToStackItem(referenceCounter); + var result = (VM.Types.Array)base.ToStackItem(referenceCounter); result.Add(Hash.ToArray()); return result; } diff --git a/src/Neo/Network/P2P/Payloads/Conditions/CalledByEntryCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/CalledByEntryCondition.cs index 4e5455af57..8f8e21f80a 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/CalledByEntryCondition.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/CalledByEntryCondition.cs @@ -1,14 +1,16 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// CalledByEntryCondition.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. using Neo.IO; +using Neo.Json; using Neo.SmartContract; using System.IO; @@ -18,10 +20,6 @@ public class CalledByEntryCondition : WitnessCondition { public override WitnessConditionType Type => WitnessConditionType.CalledByEntry; - protected override void DeserializeWithoutType(ref MemoryReader reader, int maxNestDepth) - { - } - public override bool Match(ApplicationEngine engine) { var state = engine.CurrentContext.GetState(); @@ -30,8 +28,10 @@ public override bool Match(ApplicationEngine engine) return state.CallingContext is null; } - protected override void SerializeWithoutType(BinaryWriter writer) - { - } + protected override void DeserializeWithoutType(ref MemoryReader reader, int maxNestDepth) { } + + protected override void SerializeWithoutType(BinaryWriter writer) { } + + private protected override void ParseJson(JObject json, int maxNestDepth) { } } } diff --git a/src/Neo/Network/P2P/Payloads/Conditions/CalledByGroupCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/CalledByGroupCondition.cs index d542d031db..82dab60fcf 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/CalledByGroupCondition.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/CalledByGroupCondition.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// CalledByGroupCondition.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -47,7 +48,7 @@ protected override void SerializeWithoutType(BinaryWriter writer) writer.Write(Group); } - private protected override void ParseJson(JObject json) + private protected override void ParseJson(JObject json, int maxNestDepth) { Group = ECPoint.Parse(json["group"].GetString(), ECCurve.Secp256r1); } @@ -61,7 +62,7 @@ public override JObject ToJson() public override StackItem ToStackItem(ReferenceCounter referenceCounter) { - var result = (Array)base.ToStackItem(referenceCounter); + var result = (VM.Types.Array)base.ToStackItem(referenceCounter); result.Add(Group.ToArray()); return result; } diff --git a/src/Neo/Network/P2P/Payloads/Conditions/GroupCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/GroupCondition.cs index 1acd19f190..ee937aff8b 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/GroupCondition.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/GroupCondition.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// GroupCondition.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -47,7 +48,7 @@ protected override void SerializeWithoutType(BinaryWriter writer) writer.Write(Group); } - private protected override void ParseJson(JObject json) + private protected override void ParseJson(JObject json, int maxNestDepth) { Group = ECPoint.Parse(json["group"].GetString(), ECCurve.Secp256r1); } @@ -61,7 +62,7 @@ public override JObject ToJson() public override StackItem ToStackItem(ReferenceCounter referenceCounter) { - var result = (Array)base.ToStackItem(referenceCounter); + var result = (VM.Types.Array)base.ToStackItem(referenceCounter); result.Add(Group.ToArray()); return result; } diff --git a/src/Neo/Network/P2P/Payloads/Conditions/NotCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/NotCondition.cs index 667a66770c..83ecd4d973 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/NotCondition.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/NotCondition.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// NotCondition.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -47,9 +48,10 @@ protected override void SerializeWithoutType(BinaryWriter writer) writer.Write(Expression); } - private protected override void ParseJson(JObject json) + private protected override void ParseJson(JObject json, int maxNestDepth) { - Expression = FromJson((JObject)json["expression"]); + if (maxNestDepth <= 0) throw new FormatException(); + Expression = FromJson((JObject)json["expression"], maxNestDepth - 1); } public override JObject ToJson() diff --git a/src/Neo/Network/P2P/Payloads/Conditions/OrCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/OrCondition.cs index c66becde2a..b06fc922a8 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/OrCondition.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/OrCondition.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// OrCondition.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -49,9 +50,13 @@ protected override void SerializeWithoutType(BinaryWriter writer) writer.Write(Expressions); } - private protected override void ParseJson(JObject json) + private protected override void ParseJson(JObject json, int maxNestDepth) { - Expressions = ((JArray)json["expressions"]).Select(p => FromJson((JObject)p)).ToArray(); + if (maxNestDepth <= 0) throw new FormatException(); + JArray expressions = (JArray)json["expressions"]; + if (expressions.Count > MaxSubitems) throw new FormatException(); + Expressions = expressions.Select(p => FromJson((JObject)p, maxNestDepth - 1)).ToArray(); + if (Expressions.Length == 0) throw new FormatException(); } public override JObject ToJson() diff --git a/src/Neo/Network/P2P/Payloads/Conditions/ScriptHashCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/ScriptHashCondition.cs index 315c1fde02..9199e1a9a2 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/ScriptHashCondition.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/ScriptHashCondition.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ScriptHashCondition.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -42,7 +43,7 @@ protected override void SerializeWithoutType(BinaryWriter writer) writer.Write(Hash); } - private protected override void ParseJson(JObject json) + private protected override void ParseJson(JObject json, int maxNestDepth) { Hash = UInt160.Parse(json["hash"].GetString()); } @@ -56,7 +57,7 @@ public override JObject ToJson() public override StackItem ToStackItem(ReferenceCounter referenceCounter) { - var result = (Array)base.ToStackItem(referenceCounter); + var result = (VM.Types.Array)base.ToStackItem(referenceCounter); result.Add(Hash.ToArray()); return result; } diff --git a/src/Neo/Network/P2P/Payloads/Conditions/WitnessCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/WitnessCondition.cs index ed9f55b941..7738a6d4a8 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/WitnessCondition.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/WitnessCondition.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// WitnessCondition.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -21,7 +22,7 @@ namespace Neo.Network.P2P.Payloads.Conditions { public abstract class WitnessCondition : IInteroperable, ISerializable { - private const int MaxSubitems = 16; + internal const int MaxSubitems = 16; internal const int MaxNestingDepth = 2; /// @@ -92,21 +93,20 @@ void ISerializable.Serialize(BinaryWriter writer) /// The for writing data. protected abstract void SerializeWithoutType(BinaryWriter writer); - private protected virtual void ParseJson(JObject json) - { - } + private protected abstract void ParseJson(JObject json, int maxNestDepth); /// /// Converts the from a JSON object. /// /// The represented by a JSON object. + /// The maximum nesting depth allowed during deserialization. /// The converted . - public static WitnessCondition FromJson(JObject json) + public static WitnessCondition FromJson(JObject json, int maxNestDepth) { WitnessConditionType type = Enum.Parse(json["type"].GetString()); if (ReflectionCache.CreateInstance(type) is not WitnessCondition condition) throw new FormatException("Invalid WitnessConditionType."); - condition.ParseJson(json); + condition.ParseJson(json, maxNestDepth); return condition; } diff --git a/src/Neo/Network/P2P/Payloads/Conditions/WitnessConditionType.cs b/src/Neo/Network/P2P/Payloads/Conditions/WitnessConditionType.cs index 63023ed50d..e86f7fc343 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/WitnessConditionType.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/WitnessConditionType.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// WitnessConditionType.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Network/P2P/Payloads/Conflicts.cs b/src/Neo/Network/P2P/Payloads/Conflicts.cs index db00b03259..082de2014d 100644 --- a/src/Neo/Network/P2P/Payloads/Conflicts.cs +++ b/src/Neo/Network/P2P/Payloads/Conflicts.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Conflicts.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Neo.IO; using Neo.Json; using Neo.Persistence; @@ -43,5 +54,10 @@ public override bool Verify(DataCache snapshot, Transaction tx) // on-chain transaction. return !NativeContract.Ledger.ContainsTransaction(snapshot, Hash); } + + public override long CalculateNetworkFee(DataCache snapshot, Transaction tx) + { + return tx.Signers.Length * base.CalculateNetworkFee(snapshot, tx); + } } } diff --git a/src/Neo/Network/P2P/Payloads/ExtensiblePayload.cs b/src/Neo/Network/P2P/Payloads/ExtensiblePayload.cs index faf96626d1..306f6327ab 100644 --- a/src/Neo/Network/P2P/Payloads/ExtensiblePayload.cs +++ b/src/Neo/Network/P2P/Payloads/ExtensiblePayload.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ExtensiblePayload.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Network/P2P/Payloads/FilterAddPayload.cs b/src/Neo/Network/P2P/Payloads/FilterAddPayload.cs index 0e73e4e2b0..ff4aa1287b 100644 --- a/src/Neo/Network/P2P/Payloads/FilterAddPayload.cs +++ b/src/Neo/Network/P2P/Payloads/FilterAddPayload.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// FilterAddPayload.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Network/P2P/Payloads/FilterLoadPayload.cs b/src/Neo/Network/P2P/Payloads/FilterLoadPayload.cs index dd0562df3c..2608e0e7b0 100644 --- a/src/Neo/Network/P2P/Payloads/FilterLoadPayload.cs +++ b/src/Neo/Network/P2P/Payloads/FilterLoadPayload.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// FilterLoadPayload.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Network/P2P/Payloads/GetBlockByIndexPayload.cs b/src/Neo/Network/P2P/Payloads/GetBlockByIndexPayload.cs index 115bc14c3d..9d8d2b6b6c 100644 --- a/src/Neo/Network/P2P/Payloads/GetBlockByIndexPayload.cs +++ b/src/Neo/Network/P2P/Payloads/GetBlockByIndexPayload.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// GetBlockByIndexPayload.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Network/P2P/Payloads/GetBlocksPayload.cs b/src/Neo/Network/P2P/Payloads/GetBlocksPayload.cs index ef1555a464..d12c8ae2cd 100644 --- a/src/Neo/Network/P2P/Payloads/GetBlocksPayload.cs +++ b/src/Neo/Network/P2P/Payloads/GetBlocksPayload.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// GetBlocksPayload.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Network/P2P/Payloads/Header.cs b/src/Neo/Network/P2P/Payloads/Header.cs index d971ac2aaa..9e1d77fb8a 100644 --- a/src/Neo/Network/P2P/Payloads/Header.cs +++ b/src/Neo/Network/P2P/Payloads/Header.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// Header.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -242,6 +243,7 @@ internal bool Verify(ProtocolSettings settings, DataCache snapshot) TrimmedBlock prev = NativeContract.Ledger.GetTrimmedBlock(snapshot, prevHash); if (prev is null) return false; if (prev.Index + 1 != index) return false; + if (prev.Hash != prevHash) return false; if (prev.Header.timestamp >= timestamp) return false; if (!this.VerifyWitnesses(settings, snapshot, 3_00000000L)) return false; return true; diff --git a/src/Neo/Network/P2P/Payloads/HeadersPayload.cs b/src/Neo/Network/P2P/Payloads/HeadersPayload.cs index 13be7cc82f..152ba32857 100644 --- a/src/Neo/Network/P2P/Payloads/HeadersPayload.cs +++ b/src/Neo/Network/P2P/Payloads/HeadersPayload.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// HeadersPayload.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Network/P2P/Payloads/HighPriorityAttribute.cs b/src/Neo/Network/P2P/Payloads/HighPriorityAttribute.cs index 0891dba181..cf4d3eb77e 100644 --- a/src/Neo/Network/P2P/Payloads/HighPriorityAttribute.cs +++ b/src/Neo/Network/P2P/Payloads/HighPriorityAttribute.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// HighPriorityAttribute.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Network/P2P/Payloads/IInventory.cs b/src/Neo/Network/P2P/Payloads/IInventory.cs index 43d42e2895..cab68da91e 100644 --- a/src/Neo/Network/P2P/Payloads/IInventory.cs +++ b/src/Neo/Network/P2P/Payloads/IInventory.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// IInventory.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Network/P2P/Payloads/IVerifiable.cs b/src/Neo/Network/P2P/Payloads/IVerifiable.cs index 63a5c49f53..c39661bb61 100644 --- a/src/Neo/Network/P2P/Payloads/IVerifiable.cs +++ b/src/Neo/Network/P2P/Payloads/IVerifiable.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// IVerifiable.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Network/P2P/Payloads/InvPayload.cs b/src/Neo/Network/P2P/Payloads/InvPayload.cs index 0959ecc145..aa4d340d99 100644 --- a/src/Neo/Network/P2P/Payloads/InvPayload.cs +++ b/src/Neo/Network/P2P/Payloads/InvPayload.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// InvPayload.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Network/P2P/Payloads/InventoryType.cs b/src/Neo/Network/P2P/Payloads/InventoryType.cs index 3e20cc5c79..e2aee0ca6f 100644 --- a/src/Neo/Network/P2P/Payloads/InventoryType.cs +++ b/src/Neo/Network/P2P/Payloads/InventoryType.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// InventoryType.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Network/P2P/Payloads/MerkleBlockPayload.cs b/src/Neo/Network/P2P/Payloads/MerkleBlockPayload.cs index fba40f192a..aebb17d5d9 100644 --- a/src/Neo/Network/P2P/Payloads/MerkleBlockPayload.cs +++ b/src/Neo/Network/P2P/Payloads/MerkleBlockPayload.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// MerkleBlockPayload.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Network/P2P/Payloads/NetworkAddressWithTime.cs b/src/Neo/Network/P2P/Payloads/NetworkAddressWithTime.cs index 4e64d26661..d3cfd15f66 100644 --- a/src/Neo/Network/P2P/Payloads/NetworkAddressWithTime.cs +++ b/src/Neo/Network/P2P/Payloads/NetworkAddressWithTime.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// NetworkAddressWithTime.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Network/P2P/Payloads/NotValidBefore.cs b/src/Neo/Network/P2P/Payloads/NotValidBefore.cs index e77693813b..382a60e8fc 100644 --- a/src/Neo/Network/P2P/Payloads/NotValidBefore.cs +++ b/src/Neo/Network/P2P/Payloads/NotValidBefore.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// NotValidBefore.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Neo.IO; using Neo.Json; using Neo.Persistence; diff --git a/src/Neo/Network/P2P/Payloads/OracleResponse.cs b/src/Neo/Network/P2P/Payloads/OracleResponse.cs index 0babd9933a..2770f556a8 100644 --- a/src/Neo/Network/P2P/Payloads/OracleResponse.cs +++ b/src/Neo/Network/P2P/Payloads/OracleResponse.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// OracleResponse.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Network/P2P/Payloads/OracleResponseCode.cs b/src/Neo/Network/P2P/Payloads/OracleResponseCode.cs index e2a09b205d..e94d9b1d96 100644 --- a/src/Neo/Network/P2P/Payloads/OracleResponseCode.cs +++ b/src/Neo/Network/P2P/Payloads/OracleResponseCode.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// OracleResponseCode.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Network/P2P/Payloads/PingPayload.cs b/src/Neo/Network/P2P/Payloads/PingPayload.cs index d744702c3a..f6296a7713 100644 --- a/src/Neo/Network/P2P/Payloads/PingPayload.cs +++ b/src/Neo/Network/P2P/Payloads/PingPayload.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// PingPayload.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Network/P2P/Payloads/Signer.cs b/src/Neo/Network/P2P/Payloads/Signer.cs index 321f5e16ec..79065408f4 100644 --- a/src/Neo/Network/P2P/Payloads/Signer.cs +++ b/src/Neo/Network/P2P/Payloads/Signer.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// Signer.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -152,9 +153,11 @@ public void Serialize(BinaryWriter writer) /// The converted signer. public static Signer FromJson(JObject json) { - Signer signer = new(); - signer.Account = UInt160.Parse(json["account"].GetString()); - signer.Scopes = Enum.Parse(json["scopes"].GetString()); + Signer signer = new() + { + Account = UInt160.Parse(json["account"].GetString()), + Scopes = Enum.Parse(json["scopes"].GetString()) + }; if (signer.Scopes.HasFlag(WitnessScope.CustomContracts)) signer.AllowedContracts = ((JArray)json["allowedcontracts"]).Select(p => UInt160.Parse(p.GetString())).ToArray(); if (signer.Scopes.HasFlag(WitnessScope.CustomGroups)) @@ -189,14 +192,14 @@ void IInteroperable.FromStackItem(VM.Types.StackItem stackItem) VM.Types.StackItem IInteroperable.ToStackItem(ReferenceCounter referenceCounter) { - return new VM.Types.Array(referenceCounter, new VM.Types.StackItem[] - { + return new VM.Types.Array(referenceCounter, + [ Account.ToArray(), (byte)Scopes, - new VM.Types.Array(referenceCounter, AllowedContracts.Select(u => new VM.Types.ByteString(u.ToArray()))), - new VM.Types.Array(referenceCounter, AllowedGroups.Select(u => new VM.Types.ByteString(u.ToArray()))), - new VM.Types.Array(referenceCounter, Rules.Select(u => u.ToStackItem(referenceCounter))) - }); + Scopes.HasFlag(WitnessScope.CustomContracts) ? new VM.Types.Array(referenceCounter, AllowedContracts.Select(u => new VM.Types.ByteString(u.ToArray()))) : new VM.Types.Array(referenceCounter), + Scopes.HasFlag(WitnessScope.CustomGroups) ? new VM.Types.Array(referenceCounter, AllowedGroups.Select(u => new VM.Types.ByteString(u.ToArray()))) : new VM.Types.Array(referenceCounter), + Scopes.HasFlag(WitnessScope.WitnessRules) ? new VM.Types.Array(referenceCounter, Rules.Select(u => u.ToStackItem(referenceCounter))) : new VM.Types.Array(referenceCounter) + ]); } } } diff --git a/src/Neo/Network/P2P/Payloads/Transaction.cs b/src/Neo/Network/P2P/Payloads/Transaction.cs index 24581e2b18..b922674d91 100644 --- a/src/Neo/Network/P2P/Payloads/Transaction.cs +++ b/src/Neo/Network/P2P/Payloads/Transaction.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// Transaction.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -45,7 +46,9 @@ public class Transaction : IEquatable, IInventory, IInteroperable private byte version; private uint nonce; + // In the unit of datoshi, 1 datoshi = 1e-8 GAS private long sysfee; + // In the unit of datoshi, 1 datoshi = 1e-8 GAS private long netfee; private uint validUntilBlock; private Signer[] _signers; @@ -365,29 +368,32 @@ public virtual VerifyResult VerifyStateDependent(ProtocolSettings settings, Data if (NativeContract.Policy.IsBlocked(snapshot, hash)) return VerifyResult.PolicyFail; if (!(context?.CheckTransaction(this, conflictsList, snapshot) ?? true)) return VerifyResult.InsufficientFunds; + long attributesFee = 0; foreach (TransactionAttribute attribute in Attributes) if (!attribute.Verify(snapshot, this)) return VerifyResult.InvalidAttribute; - long net_fee = NetworkFee - Size * NativeContract.Policy.GetFeePerByte(snapshot); - if (net_fee < 0) return VerifyResult.InsufficientFunds; + else + attributesFee += attribute.CalculateNetworkFee(snapshot, this); + long netFeeDatoshi = NetworkFee - (Size * NativeContract.Policy.GetFeePerByte(snapshot)) - attributesFee; + if (netFeeDatoshi < 0) return VerifyResult.InsufficientFunds; - if (net_fee > MaxVerificationGas) net_fee = MaxVerificationGas; + if (netFeeDatoshi > MaxVerificationGas) netFeeDatoshi = MaxVerificationGas; uint execFeeFactor = NativeContract.Policy.GetExecFeeFactor(snapshot); for (int i = 0; i < hashes.Length; i++) { if (IsSignatureContract(witnesses[i].VerificationScript.Span)) - net_fee -= execFeeFactor * SignatureContractCost(); + netFeeDatoshi -= execFeeFactor * SignatureContractCost(); else if (IsMultiSigContract(witnesses[i].VerificationScript.Span, out int m, out int n)) { - net_fee -= execFeeFactor * MultiSignatureContractCost(m, n); + netFeeDatoshi -= execFeeFactor * MultiSignatureContractCost(m, n); } else { - if (!this.VerifyWitness(settings, snapshot, hashes[i], witnesses[i], net_fee, out long fee)) + if (!this.VerifyWitness(settings, snapshot, hashes[i], witnesses[i], netFeeDatoshi, out long fee)) return VerifyResult.Invalid; - net_fee -= fee; + netFeeDatoshi -= fee; } - if (net_fee < 0) return VerifyResult.InsufficientFunds; + if (netFeeDatoshi < 0) return VerifyResult.InsufficientFunds; } return VerifyResult.Succeed; } @@ -454,6 +460,7 @@ public virtual VerifyResult VerifyStateIndependent(ProtocolSettings settings) public StackItem ToStackItem(ReferenceCounter referenceCounter) { + if (_signers == null || _signers.Length == 0) throw new ArgumentException("Sender is not specified in the transaction."); return new Array(referenceCounter, new StackItem[] { // Computed properties diff --git a/src/Neo/Network/P2P/Payloads/TransactionAttribute.cs b/src/Neo/Network/P2P/Payloads/TransactionAttribute.cs index da8620a95c..34af3453fe 100644 --- a/src/Neo/Network/P2P/Payloads/TransactionAttribute.cs +++ b/src/Neo/Network/P2P/Payloads/TransactionAttribute.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// TransactionAttribute.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -12,6 +13,7 @@ using Neo.IO.Caching; using Neo.Json; using Neo.Persistence; +using Neo.SmartContract.Native; using System; using System.IO; @@ -92,5 +94,7 @@ public void Serialize(BinaryWriter writer) /// The that contains the attribute. /// if the verification passes; otherwise, . public virtual bool Verify(DataCache snapshot, Transaction tx) => true; + + public virtual long CalculateNetworkFee(DataCache snapshot, Transaction tx) => NativeContract.Policy.GetAttributeFee(snapshot, (byte)Type); } } diff --git a/src/Neo/Network/P2P/Payloads/TransactionAttributeType.cs b/src/Neo/Network/P2P/Payloads/TransactionAttributeType.cs index 66dccb340b..116f136c07 100644 --- a/src/Neo/Network/P2P/Payloads/TransactionAttributeType.cs +++ b/src/Neo/Network/P2P/Payloads/TransactionAttributeType.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// TransactionAttributeType.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Network/P2P/Payloads/VersionPayload.cs b/src/Neo/Network/P2P/Payloads/VersionPayload.cs index cde644b143..8cec6278e7 100644 --- a/src/Neo/Network/P2P/Payloads/VersionPayload.cs +++ b/src/Neo/Network/P2P/Payloads/VersionPayload.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// VersionPayload.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Network/P2P/Payloads/Witness.cs b/src/Neo/Network/P2P/Payloads/Witness.cs index e8733bd9e2..34932cc7fb 100644 --- a/src/Neo/Network/P2P/Payloads/Witness.cs +++ b/src/Neo/Network/P2P/Payloads/Witness.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// Witness.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Network/P2P/Payloads/WitnessRule.cs b/src/Neo/Network/P2P/Payloads/WitnessRule.cs index a020191e79..3bac09e7f5 100644 --- a/src/Neo/Network/P2P/Payloads/WitnessRule.cs +++ b/src/Neo/Network/P2P/Payloads/WitnessRule.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// WitnessRule.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -57,10 +58,15 @@ void ISerializable.Serialize(BinaryWriter writer) /// The converted . public static WitnessRule FromJson(JObject json) { + WitnessRuleAction action = Enum.Parse(json["action"].GetString()); + + if (action != WitnessRuleAction.Allow && action != WitnessRuleAction.Deny) + throw new FormatException(); + return new() { - Action = Enum.Parse(json["action"].GetString()), - Condition = WitnessCondition.FromJson((JObject)json["condition"]) + Action = action, + Condition = WitnessCondition.FromJson((JObject)json["condition"], WitnessCondition.MaxNestingDepth) }; } diff --git a/src/Neo/Network/P2P/Payloads/WitnessRuleAction.cs b/src/Neo/Network/P2P/Payloads/WitnessRuleAction.cs index 24bed76f69..9b73879e69 100644 --- a/src/Neo/Network/P2P/Payloads/WitnessRuleAction.cs +++ b/src/Neo/Network/P2P/Payloads/WitnessRuleAction.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// WitnessRuleAction.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Network/P2P/Payloads/WitnessScope.cs b/src/Neo/Network/P2P/Payloads/WitnessScope.cs index 1ac2815af2..c43dfa0f22 100644 --- a/src/Neo/Network/P2P/Payloads/WitnessScope.cs +++ b/src/Neo/Network/P2P/Payloads/WitnessScope.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// WitnessScope.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Network/P2P/Peer.cs b/src/Neo/Network/P2P/Peer.cs index 54872640d7..767d747b1d 100644 --- a/src/Neo/Network/P2P/Peer.cs +++ b/src/Neo/Network/P2P/Peer.cs @@ -1,18 +1,16 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// Peer.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. using Akka.Actor; using Akka.IO; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; using Neo.IO; using System; using System.Buffers.Binary; @@ -23,8 +21,6 @@ using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; -using System.Net.WebSockets; -using System.Threading.Tasks; namespace Neo.Network.P2P { @@ -61,7 +57,6 @@ public class Connect } private class Timer { } - private class WsConnected { public WebSocket Socket; public IPEndPoint Remote; public IPEndPoint Local; } /// /// The default minimum number of desired connections. @@ -75,7 +70,6 @@ private class WsConnected { public WebSocket Socket; public IPEndPoint Remote; p private static readonly IActorRef tcp_manager = Context.System.Tcp(); private IActorRef tcp_listener; - private IWebHost ws_host; private ICancelable timer; private static readonly HashSet localAddresses = new(); @@ -108,11 +102,6 @@ private class WsConnected { public WebSocket Socket; public IPEndPoint Remote; p /// public int ListenerTcpPort { get; private set; } - /// - /// The port listened by the local WebSocket server. - /// - public int ListenerWsPort { get; private set; } - /// /// Indicates the maximum number of connections with the same address. /// @@ -220,9 +209,6 @@ protected override void OnReceive(object message) case Connect connect: ConnectToPeer(connect.EndPoint, connect.IsTrusted); break; - case WsConnected ws: - OnWsConnected(ws.Socket, ws.Remote, ws.Local); - break; case Tcp.Connected connected: OnTcpConnected(((IPEndPoint)connected.RemoteAddress).Unmap(), ((IPEndPoint)connected.LocalAddress).Unmap()); break; @@ -241,7 +227,6 @@ protected override void OnReceive(object message) private void OnStart(ChannelsConfig config) { ListenerTcpPort = config.Tcp?.Port ?? 0; - ListenerWsPort = config.WebSocket?.Port ?? 0; MinDesiredConnections = config.MinDesiredConnections; MaxConnections = config.MaxConnections; @@ -249,7 +234,7 @@ private void OnStart(ChannelsConfig config) // schedule time to trigger `OnTimer` event every TimerMillisecondsInterval ms timer = Context.System.Scheduler.ScheduleTellRepeatedlyCancelable(0, 5000, Context.Self, new Timer(), ActorRefs.NoSender); - if ((ListenerTcpPort > 0 || ListenerWsPort > 0) + if ((ListenerTcpPort > 0) && localAddresses.All(p => !p.IsIPv4MappedToIPv6 || IsIntranetAddress(p)) && UPnP.Discover()) { @@ -258,7 +243,6 @@ private void OnStart(ChannelsConfig config) localAddresses.Add(UPnP.GetExternalIP()); if (ListenerTcpPort > 0) UPnP.ForwardPort(ListenerTcpPort, ProtocolType.Tcp, "NEO Tcp"); - if (ListenerWsPort > 0) UPnP.ForwardPort(ListenerWsPort, ProtocolType.Tcp, "NEO WebSocket"); } catch { } } @@ -266,19 +250,6 @@ private void OnStart(ChannelsConfig config) { tcp_manager.Tell(new Tcp.Bind(Self, config.Tcp, options: new[] { new Inet.SO.ReuseAddress(true) })); } - if (ListenerWsPort > 0) - { - var host = "*"; - - if (!config.WebSocket.Address.GetAddressBytes().SequenceEqual(IPAddress.Any.GetAddressBytes())) - { - // Is not for all interfaces - host = config.WebSocket.Address.ToString(); - } - - ws_host = new WebHostBuilder().UseKestrel().UseUrls($"http://{host}:{ListenerWsPort}").Configure(app => app.UseWebSockets().Run(ProcessWebSocketAsync)).Build(); - ws_host.Start(); - } } /// @@ -367,40 +338,13 @@ private void OnTimer() } } - private void OnWsConnected(WebSocket ws, IPEndPoint remote, IPEndPoint local) - { - ConnectedAddresses.TryGetValue(remote.Address, out int count); - if (count >= MaxConnectionsPerAddress) - { - ws.Abort(); - } - else - { - ConnectedAddresses[remote.Address] = count + 1; - Context.ActorOf(ProtocolProps(ws, remote, local), $"connection_{Guid.NewGuid()}"); - } - } - protected override void PostStop() { timer.CancelIfNotNull(); - ws_host?.Dispose(); tcp_listener?.Tell(Tcp.Unbind.Instance); base.PostStop(); } - private async Task ProcessWebSocketAsync(HttpContext context) - { - if (!context.WebSockets.IsWebSocketRequest) return; - WebSocket ws = await context.WebSockets.AcceptWebSocketAsync(); - Self.Tell(new WsConnected - { - Socket = ws, - Remote = new IPEndPoint(context.Connection.RemoteIpAddress, context.Connection.RemotePort), - Local = new IPEndPoint(context.Connection.LocalIpAddress, context.Connection.LocalPort) - }); - } - /// /// Gets a object used for creating the protocol actor. /// diff --git a/src/Neo/Network/P2P/RemoteNode.ProtocolHandler.cs b/src/Neo/Network/P2P/RemoteNode.ProtocolHandler.cs index ffbc53c1cb..4606723cd0 100644 --- a/src/Neo/Network/P2P/RemoteNode.ProtocolHandler.cs +++ b/src/Neo/Network/P2P/RemoteNode.ProtocolHandler.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// RemoteNode.ProtocolHandler.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -318,7 +319,7 @@ private void OnInventoryReceived(IInventory inventory) switch (inventory) { case Transaction transaction: - if (!(system.ContainsTransaction(transaction.Hash) || system.ContainsConflictHash(transaction.Hash, transaction.Signers.Select(s => s.Account)))) + if (!(system.ContainsTransaction(transaction.Hash) != ContainsTransactionType.NotExist || system.ContainsConflictHash(transaction.Hash, transaction.Signers.Select(s => s.Account)))) system.TxRouter.Tell(new TransactionRouter.Preverify(transaction, true)); break; case Block block: diff --git a/src/Neo/Network/P2P/RemoteNode.cs b/src/Neo/Network/P2P/RemoteNode.cs index 845455aa53..a4bf0cc089 100644 --- a/src/Neo/Network/P2P/RemoteNode.cs +++ b/src/Neo/Network/P2P/RemoteNode.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// RemoteNode.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -82,8 +83,8 @@ public RemoteNode(NeoSystem system, LocalNode localNode, object connection, IPEn { this.system = system; this.localNode = localNode; - this.knownHashes = new HashSetCache(system.MemPool.Capacity * 2 / 5); - this.sentHashes = new HashSetCache(system.MemPool.Capacity * 2 / 5); + knownHashes = new HashSetCache(system.MemPool.Capacity * 2 / 5); + sentHashes = new HashSetCache(system.MemPool.Capacity * 2 / 5); localNode.RemoteNodes.TryAdd(Self, this); } @@ -208,7 +209,6 @@ private void OnStartProtocol() }; if (localNode.ListenerTcpPort > 0) capabilities.Add(new ServerCapability(NodeCapabilityType.TcpServer, (ushort)localNode.ListenerTcpPort)); - if (localNode.ListenerWsPort > 0) capabilities.Add(new ServerCapability(NodeCapabilityType.WsServer, (ushort)localNode.ListenerWsPort)); SendMessage(Message.Create(MessageCommand.Version, VersionPayload.Create(system.Settings.Network, LocalNode.Nonce, LocalNode.UserAgent, capabilities.ToArray()))); } diff --git a/src/Neo/Network/P2P/TaskManager.cs b/src/Neo/Network/P2P/TaskManager.cs index 23d5941df1..e9c12e58a4 100644 --- a/src/Neo/Network/P2P/TaskManager.cs +++ b/src/Neo/Network/P2P/TaskManager.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// TaskManager.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -73,7 +74,7 @@ private class Timer { } public TaskManager(NeoSystem system) { this.system = system; - this.knownHashes = new HashSetCache(system.MemPool.Capacity * 2 / 5); + knownHashes = new HashSetCache(system.MemPool.Capacity * 2 / 5); Context.System.EventStream.Subscribe(Self, typeof(Blockchain.PersistCompleted)); Context.System.EventStream.Subscribe(Self, typeof(Blockchain.RelayResult)); } diff --git a/src/Neo/Network/P2P/TaskSession.cs b/src/Neo/Network/P2P/TaskSession.cs index e7bea39107..a22ac7f0b4 100644 --- a/src/Neo/Network/P2P/TaskSession.cs +++ b/src/Neo/Network/P2P/TaskSession.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// TaskSession.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -30,8 +31,8 @@ internal class TaskSession public TaskSession(VersionPayload version) { var fullNode = version.Capabilities.OfType().FirstOrDefault(); - this.IsFullNode = fullNode != null; - this.LastBlockIndex = fullNode?.StartHeight ?? 0; + IsFullNode = fullNode != null; + LastBlockIndex = fullNode?.StartHeight ?? 0; } } } diff --git a/src/Neo/Network/UPnP.cs b/src/Neo/Network/UPnP.cs index 3c1ea9c5a2..a0cd0ce78a 100644 --- a/src/Neo/Network/UPnP.cs +++ b/src/Neo/Network/UPnP.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// UPnP.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -182,8 +183,8 @@ private static XmlDocument SOAPRequest(string url, string soap, string function) request.Headers.Add("Content-Type", "text/xml; charset=\"utf-8\""); request.Content = new StringContent(req); using HttpClient http = new(); - using HttpResponseMessage response = http.Send(request); - using Stream stream = response.EnsureSuccessStatusCode().Content.ReadAsStream(); + using HttpResponseMessage response = http.SendAsync(request).GetAwaiter().GetResult(); + using Stream stream = response.EnsureSuccessStatusCode().Content.ReadAsStreamAsync().GetAwaiter().GetResult(); XmlDocument resp = new() { XmlResolver = null }; resp.Load(stream); return resp; diff --git a/src/Neo/Persistence/ClonedCache.cs b/src/Neo/Persistence/ClonedCache.cs index 169dfc7e35..e2629e385d 100644 --- a/src/Neo/Persistence/ClonedCache.cs +++ b/src/Neo/Persistence/ClonedCache.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ClonedCache.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Persistence/DataCache.cs b/src/Neo/Persistence/DataCache.cs index 3c82c63eea..86fbd69961 100644 --- a/src/Neo/Persistence/DataCache.cs +++ b/src/Neo/Persistence/DataCache.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// DataCache.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -95,7 +96,7 @@ public void Add(StorageKey key, StorageItem value) { TrackState.Deleted => TrackState.Changed, TrackState.NotFound => TrackState.Added, - _ => throw new ArgumentException() + _ => throw new ArgumentException($"The element currently has state {trackable.State}") }; } else diff --git a/src/Neo/Persistence/IReadOnlyStore.cs b/src/Neo/Persistence/IReadOnlyStore.cs index 0dbf633726..4b6c3fe0ec 100644 --- a/src/Neo/Persistence/IReadOnlyStore.cs +++ b/src/Neo/Persistence/IReadOnlyStore.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// IReadOnlyStore.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Persistence/ISnapshot.cs b/src/Neo/Persistence/ISnapshot.cs index 961e31ef0d..29f1577048 100644 --- a/src/Neo/Persistence/ISnapshot.cs +++ b/src/Neo/Persistence/ISnapshot.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ISnapshot.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Persistence/IStore.cs b/src/Neo/Persistence/IStore.cs index 0d91f45ad2..37ccdf2a83 100644 --- a/src/Neo/Persistence/IStore.cs +++ b/src/Neo/Persistence/IStore.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// IStore.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Persistence/IStoreProvider.cs b/src/Neo/Persistence/IStoreProvider.cs index ef316e90c3..3714ef1e76 100644 --- a/src/Neo/Persistence/IStoreProvider.cs +++ b/src/Neo/Persistence/IStoreProvider.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// IStoreProvider.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Persistence/MemorySnapshot.cs b/src/Neo/Persistence/MemorySnapshot.cs index 586ee72e26..096431552c 100644 --- a/src/Neo/Persistence/MemorySnapshot.cs +++ b/src/Neo/Persistence/MemorySnapshot.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// MemorySnapshot.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -25,8 +26,8 @@ internal class MemorySnapshot : ISnapshot public MemorySnapshot(ConcurrentDictionary innerData) { this.innerData = innerData; - this.immutableData = innerData.ToImmutableDictionary(ByteArrayEqualityComparer.Default); - this.writeBatch = new ConcurrentDictionary(ByteArrayEqualityComparer.Default); + immutableData = innerData.ToImmutableDictionary(ByteArrayEqualityComparer.Default); + writeBatch = new ConcurrentDictionary(ByteArrayEqualityComparer.Default); } public void Commit() diff --git a/src/Neo/Persistence/MemoryStore.cs b/src/Neo/Persistence/MemoryStore.cs index 200c704ad0..191b89ea4f 100644 --- a/src/Neo/Persistence/MemoryStore.cs +++ b/src/Neo/Persistence/MemoryStore.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// MemoryStore.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -12,6 +13,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; namespace Neo.Persistence { @@ -20,33 +22,36 @@ namespace Neo.Persistence /// public class MemoryStore : IStore { - private readonly ConcurrentDictionary innerData = new(ByteArrayEqualityComparer.Default); + private readonly ConcurrentDictionary _innerData = new(ByteArrayEqualityComparer.Default); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Delete(byte[] key) { - innerData.TryRemove(key, out _); + _innerData.TryRemove(key, out _); } public void Dispose() { } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ISnapshot GetSnapshot() { - return new MemorySnapshot(innerData); + return new MemorySnapshot(_innerData); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Put(byte[] key, byte[] value) { - innerData[key[..]] = value[..]; + _innerData[key[..]] = value[..]; } public IEnumerable<(byte[] Key, byte[] Value)> Seek(byte[] keyOrPrefix, SeekDirection direction = SeekDirection.Forward) { if (direction == SeekDirection.Backward && keyOrPrefix?.Length == 0) yield break; - ByteArrayComparer comparer = direction == SeekDirection.Forward ? ByteArrayComparer.Default : ByteArrayComparer.Reverse; - IEnumerable> records = innerData; + var comparer = direction == SeekDirection.Forward ? ByteArrayComparer.Default : ByteArrayComparer.Reverse; + IEnumerable> records = _innerData; if (keyOrPrefix?.Length > 0) records = records.Where(p => comparer.Compare(p.Key, keyOrPrefix) >= 0); records = records.OrderBy(p => p.Key, comparer); @@ -54,15 +59,23 @@ public void Put(byte[] key, byte[] value) yield return (pair.Key[..], pair.Value[..]); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public byte[] TryGet(byte[] key) { - innerData.TryGetValue(key, out byte[] value); - return value?[..]; + if (!_innerData.TryGetValue(key, out byte[] value)) return null; + return value[..]; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Contains(byte[] key) { - return innerData.ContainsKey(key); + return _innerData.ContainsKey(key); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void Reset() + { + _innerData.Clear(); } } } diff --git a/src/Neo/Persistence/MemoryStoreProvider.cs b/src/Neo/Persistence/MemoryStoreProvider.cs new file mode 100644 index 0000000000..e72a13c899 --- /dev/null +++ b/src/Neo/Persistence/MemoryStoreProvider.cs @@ -0,0 +1,19 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// MemoryStoreProvider.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.Persistence +{ + public class MemoryStoreProvider : IStoreProvider + { + public string Name => nameof(MemoryStore); + public IStore GetStore(string path) => new MemoryStore(); + } +} diff --git a/src/Neo/Persistence/SeekDirection.cs b/src/Neo/Persistence/SeekDirection.cs index e7a0ea8a2e..4155ace94f 100644 --- a/src/Neo/Persistence/SeekDirection.cs +++ b/src/Neo/Persistence/SeekDirection.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// SeekDirection.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Persistence/SnapshotCache.cs b/src/Neo/Persistence/SnapshotCache.cs index e05d9e8fcc..6731e5342f 100644 --- a/src/Neo/Persistence/SnapshotCache.cs +++ b/src/Neo/Persistence/SnapshotCache.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// SnapshotCache.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -31,7 +32,7 @@ public class SnapshotCache : DataCache, IDisposable public SnapshotCache(IReadOnlyStore store) { this.store = store; - this.snapshot = store as ISnapshot; + snapshot = store as ISnapshot; } protected override void AddInternal(StorageKey key, StorageItem value) @@ -47,7 +48,7 @@ protected override void DeleteInternal(StorageKey key) public override void Commit() { base.Commit(); - snapshot.Commit(); + snapshot?.Commit(); } protected override bool ContainsInternal(StorageKey key) diff --git a/src/Neo/Persistence/StoreFactory.cs b/src/Neo/Persistence/StoreFactory.cs index c6e9fd34dc..6fee81e9f3 100644 --- a/src/Neo/Persistence/StoreFactory.cs +++ b/src/Neo/Persistence/StoreFactory.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// StoreFactory.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -14,17 +15,15 @@ namespace Neo.Persistence; public static class StoreFactory { - private class MemoryStoreProvider : IStoreProvider - { - public string Name => nameof(MemoryStore); - public IStore GetStore(string path) => new MemoryStore(); - } - private static readonly Dictionary providers = new(); static StoreFactory() { - RegisterProvider(new MemoryStoreProvider()); + var memProvider = new MemoryStoreProvider(); + RegisterProvider(memProvider); + + // Default cases + providers.Add("", memProvider); } public static void RegisterProvider(IStoreProvider provider) @@ -32,8 +31,29 @@ public static void RegisterProvider(IStoreProvider provider) providers.Add(provider.Name, provider); } - public static IStore GetStore(string storageEngine, string path) + /// + /// Get store provider by name + /// + /// Name + /// Store provider + public static IStoreProvider GetStoreProvider(string name) + { + if (providers.TryGetValue(name, out var provider)) + { + return provider; + } + + return null; + } + + /// + /// Get store from name + /// + /// The storage engine used to create the objects. If this parameter is , a default in-memory storage engine will be used. + /// The path of the storage. If is the default in-memory storage engine, this parameter is ignored. + /// The storage engine. + public static IStore GetStore(string storageProvider, string path) { - return providers[storageEngine].GetStore(path); + return providers[storageProvider].GetStore(path); } } diff --git a/src/Neo/Persistence/TrackState.cs b/src/Neo/Persistence/TrackState.cs index 73684252af..a2780d62a8 100644 --- a/src/Neo/Persistence/TrackState.cs +++ b/src/Neo/Persistence/TrackState.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// TrackState.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Plugins/Plugin.cs b/src/Neo/Plugins/Plugin.cs index c3c66ced8a..248301af56 100644 --- a/src/Neo/Plugins/Plugin.cs +++ b/src/Neo/Plugins/Plugin.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// Plugin.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -32,7 +33,7 @@ public abstract class Plugin : IDisposable /// /// The directory containing the plugin folders. Files can be contained in any subdirectory. /// - public static readonly string PluginsDirectory = Combine(GetDirectoryName(Assembly.GetEntryAssembly().Location), "Plugins"); + public static readonly string PluginsDirectory = Combine(GetDirectoryName(System.AppContext.BaseDirectory), "Plugins"); private static readonly FileSystemWatcher configWatcher; @@ -123,7 +124,7 @@ private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEven string filename = an.Name + ".dll"; string path = filename; - if (!File.Exists(path)) path = Combine(GetDirectoryName(Assembly.GetEntryAssembly().Location), filename); + if (!File.Exists(path)) path = Combine(GetDirectoryName(System.AppContext.BaseDirectory), filename); if (!File.Exists(path)) path = Combine(PluginsDirectory, filename); if (!File.Exists(path)) path = Combine(PluginsDirectory, args.RequestingAssembly.GetName().Name, filename); if (!File.Exists(path)) return null; diff --git a/src/Neo/Properties/AssemblyInfo.cs b/src/Neo/Properties/AssemblyInfo.cs index c82a7bcfc2..8d07419cae 100644 --- a/src/Neo/Properties/AssemblyInfo.cs +++ b/src/Neo/Properties/AssemblyInfo.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// AssemblyInfo.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] diff --git a/src/Neo/ProtocolSettings.cs b/src/Neo/ProtocolSettings.cs index 5373db8bf0..19011dc24c 100644 --- a/src/Neo/ProtocolSettings.cs +++ b/src/Neo/ProtocolSettings.cs @@ -1,17 +1,17 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ProtocolSettings.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. using Microsoft.Extensions.Configuration; using Neo.Cryptography.ECC; using Neo.Network.P2P.Payloads; -using Neo.SmartContract.Native; using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -24,6 +24,8 @@ namespace Neo /// public record ProtocolSettings { + private static readonly IList AllHardforks = Enum.GetValues(typeof(Hardfork)).Cast().ToArray(); + /// /// The magic number of the NEO network. /// @@ -85,14 +87,13 @@ public record ProtocolSettings public uint MaxTraceableBlocks { get; init; } /// - /// Contains the update history of all native contracts. + /// Sets the block height from which a hardfork is activated. /// - public IReadOnlyDictionary NativeUpdateHistory { get; init; } - public ImmutableDictionary Hardforks { get; init; } /// /// Indicates the amount of gas to distribute during initialization. + /// In the unit of datoshi, 1 GAS = 1e8 datoshi /// public ulong InitialGasDistribution { get; init; } @@ -105,65 +106,23 @@ public record ProtocolSettings /// /// The default protocol settings for NEO MainNet. /// - public static ProtocolSettings Default { get; } = new ProtocolSettings + public static ProtocolSettings Default { get; } = Custom ?? new ProtocolSettings { - Network = 0x334F454Eu, + Network = 0u, AddressVersion = 0x35, - StandbyCommittee = new[] - { - //Validators - ECPoint.Parse("03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", ECCurve.Secp256r1), - ECPoint.Parse("02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093", ECCurve.Secp256r1), - ECPoint.Parse("03b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a", ECCurve.Secp256r1), - ECPoint.Parse("02ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba554", ECCurve.Secp256r1), - ECPoint.Parse("024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d", ECCurve.Secp256r1), - ECPoint.Parse("02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e", ECCurve.Secp256r1), - ECPoint.Parse("02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70", ECCurve.Secp256r1), - //Other Members - ECPoint.Parse("023a36c72844610b4d34d1968662424011bf783ca9d984efa19a20babf5582f3fe", ECCurve.Secp256r1), - ECPoint.Parse("03708b860c1de5d87f5b151a12c2a99feebd2e8b315ee8e7cf8aa19692a9e18379", ECCurve.Secp256r1), - ECPoint.Parse("03c6aa6e12638b36e88adc1ccdceac4db9929575c3e03576c617c49cce7114a050", ECCurve.Secp256r1), - ECPoint.Parse("03204223f8c86b8cd5c89ef12e4f0dbb314172e9241e30c9ef2293790793537cf0", ECCurve.Secp256r1), - ECPoint.Parse("02a62c915cf19c7f19a50ec217e79fac2439bbaad658493de0c7d8ffa92ab0aa62", ECCurve.Secp256r1), - ECPoint.Parse("03409f31f0d66bdc2f70a9730b66fe186658f84a8018204db01c106edc36553cd0", ECCurve.Secp256r1), - ECPoint.Parse("0288342b141c30dc8ffcde0204929bb46aed5756b41ef4a56778d15ada8f0c6654", ECCurve.Secp256r1), - ECPoint.Parse("020f2887f41474cfeb11fd262e982051c1541418137c02a0f4961af911045de639", ECCurve.Secp256r1), - ECPoint.Parse("0222038884bbd1d8ff109ed3bdef3542e768eef76c1247aea8bc8171f532928c30", ECCurve.Secp256r1), - ECPoint.Parse("03d281b42002647f0113f36c7b8efb30db66078dfaaa9ab3ff76d043a98d512fde", ECCurve.Secp256r1), - ECPoint.Parse("02504acbc1f4b3bdad1d86d6e1a08603771db135a73e61c9d565ae06a1938cd2ad", ECCurve.Secp256r1), - ECPoint.Parse("0226933336f1b75baa42d42b71d9091508b638046d19abd67f4e119bf64a7cfb4d", ECCurve.Secp256r1), - ECPoint.Parse("03cdcea66032b82f5c30450e381e5295cae85c5e6943af716cc6b646352a6067dc", ECCurve.Secp256r1), - ECPoint.Parse("02cd5a5547119e24feaa7c2a0f37b8c9366216bab7054de0065c9be42084003c8a", ECCurve.Secp256r1) - }, - ValidatorsCount = 7, - SeedList = new[] - { - "seed1.neo.org:10333", - "seed2.neo.org:10333", - "seed3.neo.org:10333", - "seed4.neo.org:10333", - "seed5.neo.org:10333" - }, + StandbyCommittee = Array.Empty(), + ValidatorsCount = 0, + SeedList = Array.Empty(), MillisecondsPerBlock = 15000, MaxTransactionsPerBlock = 512, MemoryPoolMaxTransactions = 50_000, MaxTraceableBlocks = 2_102_400, InitialGasDistribution = 52_000_000_00000000, - NativeUpdateHistory = new Dictionary - { - [nameof(ContractManagement)] = new[] { 0u }, - [nameof(StdLib)] = new[] { 0u }, - [nameof(CryptoLib)] = new[] { 0u }, - [nameof(LedgerContract)] = new[] { 0u }, - [nameof(NeoToken)] = new[] { 0u }, - [nameof(GasToken)] = new[] { 0u }, - [nameof(PolicyContract)] = new[] { 0u }, - [nameof(RoleManagement)] = new[] { 0u }, - [nameof(OracleContract)] = new[] { 0u } - }, - Hardforks = ImmutableDictionary.Empty + Hardforks = EnsureOmmitedHardforks(new Dictionary()).ToImmutableDictionary() }; + public static ProtocolSettings Custom { get; set; } + /// /// Loads the at the specified path. /// @@ -186,7 +145,7 @@ public static ProtocolSettings Load(string path, bool optional = true) /// The loaded . public static ProtocolSettings Load(IConfigurationSection section) { - return new ProtocolSettings + Custom = new ProtocolSettings { Network = section.GetValue("Network", Default.Network), AddressVersion = section.GetValue("AddressVersion", Default.AddressVersion), @@ -202,13 +161,33 @@ public static ProtocolSettings Load(IConfigurationSection section) MemoryPoolMaxTransactions = section.GetValue("MemoryPoolMaxTransactions", Default.MemoryPoolMaxTransactions), MaxTraceableBlocks = section.GetValue("MaxTraceableBlocks", Default.MaxTraceableBlocks), InitialGasDistribution = section.GetValue("InitialGasDistribution", Default.InitialGasDistribution), - NativeUpdateHistory = section.GetSection("NativeUpdateHistory").Exists() - ? section.GetSection("NativeUpdateHistory").GetChildren().ToDictionary(p => p.Key, p => p.GetChildren().Select(q => uint.Parse(q.Value)).ToArray()) - : Default.NativeUpdateHistory, Hardforks = section.GetSection("Hardforks").Exists() - ? section.GetSection("Hardforks").GetChildren().ToImmutableDictionary(p => Enum.Parse(p.Key), p => uint.Parse(p.Value)) + ? EnsureOmmitedHardforks(section.GetSection("Hardforks").GetChildren().ToDictionary(p => Enum.Parse(p.Key, true), p => uint.Parse(p.Value))).ToImmutableDictionary() : Default.Hardforks }; + return Custom; + } + + /// + /// Explicitly set the height of all old omitted hardforks to 0 for proper IsHardforkEnabled behaviour. + /// + /// HardForks + /// Processed hardfork configuration + private static Dictionary EnsureOmmitedHardforks(Dictionary hardForks) + { + foreach (Hardfork hf in AllHardforks) + { + if (!hardForks.ContainsKey(hf)) + { + hardForks[hf] = 0; + } + else + { + break; + } + } + + return hardForks; } private static void CheckingHardfork(ProtocolSettings settings) @@ -216,7 +195,7 @@ private static void CheckingHardfork(ProtocolSettings settings) var allHardforks = Enum.GetValues(typeof(Hardfork)).Cast().ToList(); // Check for continuity in configured hardforks var sortedHardforks = settings.Hardforks.Keys - .OrderBy(h => allHardforks.IndexOf(h)) + .OrderBy(allHardforks.IndexOf) .ToList(); for (int i = 0; i < sortedHardforks.Count - 1; i++) @@ -226,7 +205,7 @@ private static void CheckingHardfork(ProtocolSettings settings) // If they aren't consecutive, return false. if (nextIndex - currentIndex > 1) - throw new Exception("Hardfork configuration is not continuous."); + throw new ArgumentException("Hardfork configuration is not continuous."); } // Check that block numbers are not higher in earlier hardforks than in later ones for (int i = 0; i < sortedHardforks.Count - 1; i++) @@ -234,9 +213,27 @@ private static void CheckingHardfork(ProtocolSettings settings) if (settings.Hardforks[sortedHardforks[i]] > settings.Hardforks[sortedHardforks[i + 1]]) { // This means the block number for the current hardfork is greater than the next one, which should not be allowed. - throw new Exception($"The Hardfork configuration for {sortedHardforks[i]} is greater than for {sortedHardforks[i + 1]}"); + throw new ArgumentException($"The Hardfork configuration for {sortedHardforks[i]} is greater than for {sortedHardforks[i + 1]}"); } } } + + /// + /// Check if the Hardfork is Enabled + /// + /// Hardfork + /// Block index + /// True if enabled + public bool IsHardforkEnabled(Hardfork hardfork, uint index) + { + if (Hardforks.TryGetValue(hardfork, out uint height)) + { + // If the hardfork has a specific height in the configuration, check the block height. + return index >= height; + } + + // If the hardfork isn't specified in the configuration, return false. + return false; + } } } diff --git a/src/Neo/SmartContract/ApplicationEngine.Contract.cs b/src/Neo/SmartContract/ApplicationEngine.Contract.cs index f3d33e7d77..e5c1c762a7 100644 --- a/src/Neo/SmartContract/ApplicationEngine.Contract.cs +++ b/src/Neo/SmartContract/ApplicationEngine.Contract.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ApplicationEngine.Contract.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -53,13 +54,13 @@ partial class ApplicationEngine /// The of System.Contract.NativeOnPersist. /// /// Note: It is for internal use only. Do not use it directly in smart contracts. - public static readonly InteropDescriptor System_Contract_NativeOnPersist = Register("System.Contract.NativeOnPersist", nameof(NativeOnPersist), 0, CallFlags.States); + public static readonly InteropDescriptor System_Contract_NativeOnPersist = Register("System.Contract.NativeOnPersist", nameof(NativeOnPersistAsync), 0, CallFlags.States); /// /// The of System.Contract.NativePostPersist. /// /// Note: It is for internal use only. Do not use it directly in smart contracts. - public static readonly InteropDescriptor System_Contract_NativePostPersist = Register("System.Contract.NativePostPersist", nameof(NativePostPersist), 0, CallFlags.States); + public static readonly InteropDescriptor System_Contract_NativePostPersist = Register("System.Contract.NativePostPersist", nameof(NativePostPersistAsync), 0, CallFlags.States); /// /// The implementation of System.Contract.Call. @@ -76,7 +77,7 @@ protected internal void CallContract(UInt160 contractHash, string method, CallFl throw new ArgumentOutOfRangeException(nameof(callFlags)); ContractState contract = NativeContract.ContractManagement.GetContract(Snapshot, contractHash); - if (contract is null) throw new InvalidOperationException($"Called Contract Does Not Exist: {contractHash}"); + if (contract is null) throw new InvalidOperationException($"Called Contract Does Not Exist: {contractHash}.{method}"); ContractMethodDescriptor md = contract.Manifest.Abi.GetMethod(method, args.Count); if (md is null) throw new InvalidOperationException($"Method \"{method}\" with {args.Count} parameter(s) doesn't exist in the contract {contractHash}."); bool hasReturnValue = md.ReturnType != ContractParameterType.Void; @@ -95,10 +96,7 @@ protected internal void CallNativeContract(byte version) NativeContract contract = NativeContract.GetContract(CurrentScriptHash); if (contract is null) throw new InvalidOperationException("It is not allowed to use \"System.Contract.CallNative\" directly."); - uint[] updates = ProtocolSettings.NativeUpdateHistory[contract.Name]; - if (updates.Length == 0) - throw new InvalidOperationException($"The native contract {contract.Name} is not active."); - if (updates[0] > NativeContract.Ledger.CurrentIndex(Snapshot)) + if (!contract.IsActive(ProtocolSettings, NativeContract.Ledger.CurrentIndex(Snapshot))) throw new InvalidOperationException($"The native contract {contract.Name} is not active."); contract.Invoke(this, version); } @@ -122,10 +120,11 @@ protected internal CallFlags GetCallFlags() /// The hash of the account. internal protected UInt160 CreateStandardAccount(ECPoint pubKey) { + // In the unit of datoshi, 1 datoshi = 1e-8 GAS long fee = IsHardforkEnabled(Hardfork.HF_Aspidochelone) ? CheckSigPrice : 1 << 8; - AddGas(fee * ExecFeeFactor); + AddFee(fee * ExecFeeFactor); return Contract.CreateSignatureRedeemScript(pubKey).ToScriptHash(); } @@ -138,18 +137,19 @@ internal protected UInt160 CreateStandardAccount(ECPoint pubKey) /// The hash of the account. internal protected UInt160 CreateMultisigAccount(int m, ECPoint[] pubKeys) { + // In the unit of datoshi, 1 datoshi = 1e-8 GAS long fee = IsHardforkEnabled(Hardfork.HF_Aspidochelone) ? CheckSigPrice * pubKeys.Length : 1 << 8; - AddGas(fee * ExecFeeFactor); + AddFee(fee * ExecFeeFactor); return Contract.CreateMultiSigRedeemScript(m, pubKeys).ToScriptHash(); } /// /// The implementation of System.Contract.NativeOnPersist. - /// Calls to the of all native contracts. + /// Calls to the of all native contracts. /// - protected internal async void NativeOnPersist() + protected internal async void NativeOnPersistAsync() { try { @@ -157,10 +157,8 @@ protected internal async void NativeOnPersist() throw new InvalidOperationException(); foreach (NativeContract contract in NativeContract.Contracts) { - uint[] updates = ProtocolSettings.NativeUpdateHistory[contract.Name]; - if (updates.Length == 0) continue; - if (updates[0] <= PersistingBlock.Index) - await contract.OnPersist(this); + if (contract.IsActive(ProtocolSettings, PersistingBlock.Index)) + await contract.OnPersistAsync(this); } } catch (Exception ex) @@ -171,9 +169,9 @@ protected internal async void NativeOnPersist() /// /// The implementation of System.Contract.NativePostPersist. - /// Calls to the of all native contracts. + /// Calls to the of all native contracts. /// - protected internal async void NativePostPersist() + protected internal async void NativePostPersistAsync() { try { @@ -181,10 +179,8 @@ protected internal async void NativePostPersist() throw new InvalidOperationException(); foreach (NativeContract contract in NativeContract.Contracts) { - uint[] updates = ProtocolSettings.NativeUpdateHistory[contract.Name]; - if (updates.Length == 0) continue; - if (updates[0] <= PersistingBlock.Index) - await contract.PostPersist(this); + if (contract.IsActive(ProtocolSettings, PersistingBlock.Index)) + await contract.PostPersistAsync(this); } } catch (Exception ex) diff --git a/src/Neo/SmartContract/ApplicationEngine.Crypto.cs b/src/Neo/SmartContract/ApplicationEngine.Crypto.cs index 7d7f1ec8d4..b63f1287f0 100644 --- a/src/Neo/SmartContract/ApplicationEngine.Crypto.cs +++ b/src/Neo/SmartContract/ApplicationEngine.Crypto.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ApplicationEngine.Crypto.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -19,6 +20,7 @@ partial class ApplicationEngine { /// /// The price of System.Crypto.CheckSig. + /// In the unit of datoshi, 1 datoshi = 1e-8 GAS /// public const long CheckSigPrice = 1 << 15; @@ -65,7 +67,7 @@ protected internal bool CheckMultisig(byte[][] pubkeys, byte[][] signatures) byte[] message = ScriptContainer.GetSignData(ProtocolSettings.Network); int m = signatures.Length, n = pubkeys.Length; if (n == 0 || m == 0 || m > n) throw new ArgumentException(); - AddGas(CheckSigPrice * n * ExecFeeFactor); + AddFee(CheckSigPrice * n * ExecFeeFactor); try { for (int i = 0, j = 0; i < m && j < n;) diff --git a/src/Neo/SmartContract/ApplicationEngine.Iterator.cs b/src/Neo/SmartContract/ApplicationEngine.Iterator.cs index d9d6f54f18..2ac9df7474 100644 --- a/src/Neo/SmartContract/ApplicationEngine.Iterator.cs +++ b/src/Neo/SmartContract/ApplicationEngine.Iterator.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ApplicationEngine.Iterator.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/SmartContract/ApplicationEngine.OpCodePrices.cs b/src/Neo/SmartContract/ApplicationEngine.OpCodePrices.cs index 5d8ea68016..17e4bd6bfb 100644 --- a/src/Neo/SmartContract/ApplicationEngine.OpCodePrices.cs +++ b/src/Neo/SmartContract/ApplicationEngine.OpCodePrices.cs @@ -1,14 +1,16 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ApplicationEngine.OpCodePrices.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. using Neo.VM; +using System; using System.Collections.Generic; namespace Neo.SmartContract @@ -18,6 +20,7 @@ partial class ApplicationEngine /// /// The prices of all the opcodes. /// + [Obsolete("You should use OpCodePriceTable")] public static readonly IReadOnlyDictionary OpCodePrices = new Dictionary { [OpCode.PUSHINT8] = 1 << 0, @@ -217,5 +220,24 @@ partial class ApplicationEngine [OpCode.ISTYPE] = 1 << 1, [OpCode.CONVERT] = 1 << 13, }; + + /// + /// The prices of all the opcodes. + /// In the unit of datoshi, 1 datoshi = 1e-8 GAS + /// + public static readonly long[] OpCodePriceTable = new long[byte.MaxValue]; + + /// + /// Init OpCodePrices + /// + static ApplicationEngine() + { +#pragma warning disable CS0618 // Type or member is obsolete + foreach (var entry in OpCodePrices) +#pragma warning restore CS0618 // Type or member is obsolete + { + OpCodePriceTable[(byte)entry.Key] = entry.Value; + } + } } } diff --git a/src/Neo/SmartContract/ApplicationEngine.Runtime.cs b/src/Neo/SmartContract/ApplicationEngine.Runtime.cs index 88d07349f9..e54529f6d1 100644 --- a/src/Neo/SmartContract/ApplicationEngine.Runtime.cs +++ b/src/Neo/SmartContract/ApplicationEngine.Runtime.cs @@ -1,24 +1,25 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ApplicationEngine.Runtime.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Numerics; using Neo.Cryptography.ECC; using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.SmartContract.Native; using Neo.VM; using Neo.VM.Types; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Numerics; using Array = Neo.VM.Types.Array; namespace Neo.SmartContract @@ -145,6 +146,12 @@ partial class ApplicationEngine /// public static readonly InteropDescriptor System_Runtime_BurnGas = Register("System.Runtime.BurnGas", nameof(BurnGas), 1 << 4, CallFlags.None); + /// + /// The of System.Runtime.CurrentSigners. + /// Get the Signers of the current transaction. + /// + public static readonly InteropDescriptor System_Runtime_CurrentSigners = Register("System.Runtime.CurrentSigners", nameof(GetCurrentSigners), 1 << 4, CallFlags.None); + /// /// The implementation of System.Runtime.Platform. /// Gets the name of the current platform. @@ -229,7 +236,7 @@ protected internal bool CheckWitness(byte[] hashOrPubkey) { 20 => new UInt160(hashOrPubkey), 33 => Contract.CreateSignatureRedeemScript(ECPoint.DecodePoint(hashOrPubkey, ECCurve.Secp256r1)).ToScriptHash(), - _ => throw new ArgumentException(null, nameof(hashOrPubkey)) + _ => throw new ArgumentException("Invalid hashOrPubkey.", nameof(hashOrPubkey)) }; return CheckWitnessInternal(hash); } @@ -266,14 +273,14 @@ protected internal bool CheckWitnessInternal(UInt160 hash) return false; } - // Check allow state callflag + // If we don't have the ScriptContainer, we consider that there are no script hashes for verifying + if (ScriptContainer is null) return false; + // Check allow state callflag ValidateCallFlags(CallFlags.ReadStates); // only for non-Transaction types (Block, etc) - - var hashes_for_verifying = ScriptContainer.GetScriptHashesForVerifying(Snapshot); - return hashes_for_verifying.Contains(hash); + return ScriptContainer.GetScriptHashesForVerifying(Snapshot).Contains(hash); } /// @@ -298,6 +305,7 @@ protected internal int GetInvocationCounter() protected internal BigInteger GetRandom() { byte[] buffer; + // In the unit of datoshi, 1 datoshi = 1e-8 GAS long price; if (IsHardforkEnabled(Hardfork.HF_Aspidochelone)) { @@ -309,7 +317,7 @@ protected internal BigInteger GetRandom() buffer = nonceData = Cryptography.Helper.Murmur128(nonceData, ProtocolSettings.Network); price = 1 << 4; } - AddGas(price * ExecFeeFactor); + AddFee(price * ExecFeeFactor); return new BigInteger(buffer, isUnsigned: true); } @@ -320,9 +328,16 @@ protected internal BigInteger GetRandom() /// The message of the log. protected internal void RuntimeLog(byte[] state) { - if (state.Length > MaxNotificationSize) throw new ArgumentException(null, nameof(state)); - string message = Utility.StrictUTF8.GetString(state); - Log?.Invoke(this, new LogEventArgs(ScriptContainer, CurrentScriptHash, message)); + if (state.Length > MaxNotificationSize) throw new ArgumentException("Message is too long.", nameof(state)); + try + { + string message = Utility.StrictUTF8.GetString(state); + Log?.Invoke(this, new LogEventArgs(ScriptContainer, CurrentScriptHash, message)); + } + catch + { + throw new ArgumentException("Failed to convert byte array to string: Invalid or non-printable UTF-8 sequence detected.", nameof(state)); + } } /// @@ -356,7 +371,7 @@ protected internal void RuntimeNotify(byte[] eventName, Array state) } using MemoryStream ms = new(MaxNotificationSize); using BinaryWriter writer = new(ms, Utility.StrictUTF8, true); - BinarySerializer.Serialize(writer, state, MaxNotificationSize); + BinarySerializer.Serialize(writer, state, MaxNotificationSize, Limits.MaxStackSize); SendNotification(CurrentScriptHash, name, state); } @@ -367,7 +382,7 @@ protected internal void RuntimeNotifyV1(byte[] eventName, Array state) throw new InvalidOperationException("Notifications are not allowed in dynamic scripts."); using MemoryStream ms = new(MaxNotificationSize); using BinaryWriter writer = new(ms, Utility.StrictUTF8, true); - BinarySerializer.Serialize(writer, state, MaxNotificationSize); + BinarySerializer.Serialize(writer, state, MaxNotificationSize, Limits.MaxStackSize); SendNotification(CurrentScriptHash, Utility.StrictUTF8.GetString(eventName), state); } @@ -392,26 +407,43 @@ protected internal void SendNotification(UInt160 hash, string eventName, Array s /// /// The hash of the specified contract. It can be set to to get all notifications. /// The notifications sent during the execution. - protected internal NotifyEventArgs[] GetNotifications(UInt160 hash) + protected internal Array GetNotifications(UInt160 hash) { IEnumerable notifications = Notifications; if (hash != null) // must filter by scriptHash notifications = notifications.Where(p => p.ScriptHash == hash); - NotifyEventArgs[] array = notifications.ToArray(); + var array = notifications.ToArray(); if (array.Length > Limits.MaxStackSize) throw new InvalidOperationException(); - return array; + Array notifyArray = new(ReferenceCounter); + foreach (var notify in array) + { + notifyArray.Add(notify.ToStackItem(ReferenceCounter, this)); + } + return notifyArray; } /// /// The implementation of System.Runtime.BurnGas. /// Burning GAS to benefit the NEO ecosystem. /// - /// The amount of GAS to burn. - protected internal void BurnGas(long gas) + /// The amount of GAS to burn, in the unit of datoshi, 1 datoshi = 1e-8 GAS + protected internal void BurnGas(long datoshi) { - if (gas <= 0) + if (datoshi <= 0) throw new InvalidOperationException("GAS must be positive."); - AddGas(gas); + AddFee(datoshi); + } + + /// + /// Get the Signers of the current transaction. + /// + /// The signers of the current transaction, or null if is not related to a transaction execution. + protected internal Signer[] GetCurrentSigners() + { + if (ScriptContainer is Transaction tx) + return tx.Signers; + + return null; } private static bool CheckItemType(StackItem item, ContractParameterType type) diff --git a/src/Neo/SmartContract/ApplicationEngine.Storage.cs b/src/Neo/SmartContract/ApplicationEngine.Storage.cs index cfa323e227..fbd452d6cc 100644 --- a/src/Neo/SmartContract/ApplicationEngine.Storage.cs +++ b/src/Neo/SmartContract/ApplicationEngine.Storage.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ApplicationEngine.Storage.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -105,7 +106,7 @@ protected internal StorageContext GetReadOnlyContext() /// /// The storage context to convert. /// The readonly storage context. - internal protected static StorageContext AsReadOnly(StorageContext context) + protected internal static StorageContext AsReadOnly(StorageContext context) { if (!context.IsReadOnly) context = new StorageContext @@ -166,8 +167,9 @@ protected internal IIterator Find(StorageContext context, byte[] prefix, FindOpt /// The value of the entry. protected internal void Put(StorageContext context, byte[] key, byte[] value) { - if (key.Length > MaxStorageKeySize || value.Length > MaxStorageValueSize || context.IsReadOnly) - throw new ArgumentException(); + if (key.Length > MaxStorageKeySize) throw new ArgumentException("Key length too big", nameof(key)); + if (value.Length > MaxStorageValueSize) throw new ArgumentException("Value length too big", nameof(value)); + if (context.IsReadOnly) throw new ArgumentException("StorageContext is readonly", nameof(context)); int newDataSize; StorageKey skey = new() @@ -192,7 +194,7 @@ protected internal void Put(StorageContext context, byte[] key, byte[] value) else newDataSize = (item.Value.Length - 1) / 4 + 1 + value.Length - item.Value.Length; } - AddGas(newDataSize * StoragePrice); + AddFee(newDataSize * StoragePrice); item.Value = value; } diff --git a/src/Neo/SmartContract/ApplicationEngine.cs b/src/Neo/SmartContract/ApplicationEngine.cs index a67b622200..fab17a2bb1 100644 --- a/src/Neo/SmartContract/ApplicationEngine.cs +++ b/src/Neo/SmartContract/ApplicationEngine.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ApplicationEngine.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -31,8 +32,11 @@ namespace Neo.SmartContract /// public partial class ApplicationEngine : ExecutionEngine { + protected static readonly JumpTable DefaultJumpTable = ComposeDefaultJumpTable(); + /// /// The maximum cost that can be spent when a contract is executed in test mode. + /// In the unit of datoshi, 1 datoshi = 1e-8 GAS /// public const long TestModeGas = 20_00000000; @@ -46,9 +50,10 @@ public partial class ApplicationEngine : ExecutionEngine /// public static event EventHandler Log; - private static readonly IList AllHardforks = Enum.GetValues(typeof(Hardfork)).Cast().ToArray(); private static Dictionary services; - private readonly long gas_amount; + // Total amount of GAS spent to execute. + // In the unit of datoshi, 1 datoshi = 1e-8 GAS, 1 GAS = 1e8 datoshi + private readonly long _feeAmount; private Dictionary states; private readonly DataCache originalSnapshot; private List notifications; @@ -56,6 +61,7 @@ public partial class ApplicationEngine : ExecutionEngine private readonly Dictionary invocationCounter = new(); private readonly Dictionary contractTasks = new(); internal readonly uint ExecFeeFactor; + // In the unit of datoshi, 1 datoshi = 1e-8 GAS internal readonly uint StoragePrice; private byte[] nonceData; @@ -103,13 +109,22 @@ public partial class ApplicationEngine : ExecutionEngine /// /// GAS spent to execute. + /// In the unit of datoshi, 1 datoshi = 1e-8 GAS, 1 GAS = 1e8 datoshi /// + [Obsolete("This property is deprecated. Use FeeConsumed instead.")] public long GasConsumed { get; private set; } = 0; + /// + /// GAS spent to execute. + /// In the unit of datoshi, 1 datoshi = 1e-8 GAS, 1 GAS = 1e8 datoshi + /// + public long FeeConsumed { get; private set; } = 0; + /// /// The remaining GAS that can be spent in order to complete the execution. + /// In the unit of datoshi, 1 datoshi = 1e-8 GAS, 1 GAS = 1e8 datoshi /// - public long GasLeft => gas_amount - GasConsumed; + public long GasLeft => _feeAmount - FeeConsumed; /// /// The exception that caused the execution to terminate abnormally. This field could be if no exception is thrown. @@ -124,7 +139,7 @@ public partial class ApplicationEngine : ExecutionEngine /// /// The script hash of the calling contract. This field could be if the current context is the entry context. /// - public UInt160 CallingScriptHash + public virtual UInt160 CallingScriptHash { get { @@ -137,7 +152,7 @@ public UInt160 CallingScriptHash /// /// The script hash of the entry context. This field could be if no context is loaded to the engine. /// - public UInt160 EntryScriptHash => EntryContext?.GetScriptHash(); + public virtual UInt160 EntryScriptHash => EntryContext?.GetScriptHash(); /// /// The notifications sent during the execution. @@ -152,20 +167,24 @@ public UInt160 CallingScriptHash /// The snapshot used by the engine during execution. /// The block being persisted. It should be if the is . /// The used by the engine. - /// The maximum gas used in this execution. The execution will fail when the gas is exhausted. + /// The maximum gas, in the unit of datoshi, used in this execution. The execution will fail when the gas is exhausted. /// The diagnostic to be used by the . - protected unsafe ApplicationEngine(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock, ProtocolSettings settings, long gas, IDiagnostic diagnostic) + /// The jump table to be used by the . + protected unsafe ApplicationEngine( + TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock, + ProtocolSettings settings, long gas, IDiagnostic diagnostic, JumpTable jumpTable = null) + : base(jumpTable ?? DefaultJumpTable) { - this.Trigger = trigger; - this.ScriptContainer = container; - this.originalSnapshot = snapshot; - this.PersistingBlock = persistingBlock; - this.ProtocolSettings = settings; - this.gas_amount = gas; - this.Diagnostic = diagnostic; - this.ExecFeeFactor = snapshot is null || persistingBlock?.Index == 0 ? PolicyContract.DefaultExecFeeFactor : NativeContract.Policy.GetExecFeeFactor(snapshot); - this.StoragePrice = snapshot is null || persistingBlock?.Index == 0 ? PolicyContract.DefaultStoragePrice : NativeContract.Policy.GetStoragePrice(snapshot); - this.nonceData = container is Transaction tx ? tx.Hash.ToArray()[..16] : new byte[16]; + Trigger = trigger; + ScriptContainer = container; + originalSnapshot = snapshot; + PersistingBlock = persistingBlock; + ProtocolSettings = settings; + _feeAmount = gas; + Diagnostic = diagnostic; + ExecFeeFactor = snapshot is null || persistingBlock?.Index == 0 ? PolicyContract.DefaultExecFeeFactor : NativeContract.Policy.GetExecFeeFactor(snapshot); + StoragePrice = snapshot is null || persistingBlock?.Index == 0 ? PolicyContract.DefaultStoragePrice : NativeContract.Policy.GetStoragePrice(snapshot); + nonceData = container is Transaction tx ? tx.Hash.ToArray()[..16] : new byte[16]; if (persistingBlock is not null) { fixed (byte* p = nonceData) @@ -176,14 +195,68 @@ protected unsafe ApplicationEngine(TriggerType trigger, IVerifiable container, D diagnostic?.Initialized(this); } + #region JumpTable + + private static JumpTable ComposeDefaultJumpTable() + { + var table = new JumpTable(); + + table[OpCode.SYSCALL] = OnSysCall; + table[OpCode.CALLT] = OnCallT; + + return table; + } + + protected static void OnCallT(ExecutionEngine engine, Instruction instruction) + { + if (engine is ApplicationEngine app) + { + uint tokenId = instruction.TokenU16; + + app.ValidateCallFlags(CallFlags.ReadStates | CallFlags.AllowCall); + ContractState contract = app.CurrentContext.GetState().Contract; + if (contract is null || tokenId >= contract.Nef.Tokens.Length) + throw new InvalidOperationException(); + MethodToken token = contract.Nef.Tokens[tokenId]; + if (token.ParametersCount > app.CurrentContext.EvaluationStack.Count) + throw new InvalidOperationException(); + StackItem[] args = new StackItem[token.ParametersCount]; + for (int i = 0; i < token.ParametersCount; i++) + args[i] = app.Pop(); + app.CallContractInternal(token.Hash, token.Method, token.CallFlags, token.HasReturnValue, args); + } + else + { + throw new InvalidOperationException(); + } + } + + protected static void OnSysCall(ExecutionEngine engine, Instruction instruction) + { + if (engine is ApplicationEngine app) + { + uint method = instruction.TokenU32; + + app.OnSysCall(services[method]); + } + else + { + throw new InvalidOperationException(); + } + } + + #endregion + /// - /// Adds GAS to and checks if it has exceeded the maximum limit. + /// Adds GAS to and checks if it has exceeded the maximum limit. /// - /// The amount of GAS to be added. - protected internal void AddGas(long gas) + /// The amount of GAS, in the unit of datoshi, 1 datoshi = 1e-8 GAS, to be added. + protected internal void AddFee(long datoshi) { - GasConsumed = checked(GasConsumed + gas); - if (GasConsumed > gas_amount) +#pragma warning disable CS0618 // Type or member is obsolete + FeeConsumed = GasConsumed = checked(FeeConsumed + datoshi); +#pragma warning restore CS0618 // Type or member is obsolete + if (FeeConsumed > _feeAmount) throw new InvalidOperationException("Insufficient GAS."); } @@ -213,14 +286,18 @@ private ExecutionContext CallContractInternal(ContractState contract, ContractMe if (NativeContract.Policy.IsBlocked(Snapshot, contract.Hash)) throw new InvalidOperationException($"The contract {contract.Hash} has been blocked."); + ExecutionContext currentContext = CurrentContext; + ExecutionContextState state = currentContext.GetState(); if (method.Safe) { flags &= ~(CallFlags.WriteStates | CallFlags.AllowNotify); } else { - ContractState currentContract = NativeContract.ContractManagement.GetContract(Snapshot, CurrentScriptHash); - if (currentContract?.CanCall(contract, method.Name) == false) + var executingContract = IsHardforkEnabled(Hardfork.HF_Domovoi) + ? state.Contract // use executing contract state to avoid possible contract update/destroy side-effects, ref. https://github.com/neo-project/neo/pull/3290. + : NativeContract.ContractManagement.GetContract(Snapshot, CurrentScriptHash); + if (executingContract?.CanCall(contract, method.Name) == false) throw new InvalidOperationException($"Cannot Call Method {method.Name} Of Contract {contract.Hash} From Contract {CurrentScriptHash}"); } @@ -233,8 +310,6 @@ private ExecutionContext CallContractInternal(ContractState contract, ContractMe invocationCounter[contract.Hash] = 1; } - ExecutionContext currentContext = CurrentContext; - ExecutionContextState state = currentContext.GetState(); CallFlags callingFlags = state.CallFlags; if (args.Count != method.Parameters.Length) throw new InvalidOperationException($"Method {method} Expects {method.Parameters.Length} Arguments But Receives {args.Count} Arguments"); @@ -249,7 +324,7 @@ private ExecutionContext CallContractInternal(ContractState contract, ContractMe return context_new; } - internal ContractTask CallFromNativeContract(UInt160 callingScriptHash, UInt160 hash, string method, params StackItem[] args) + internal ContractTask CallFromNativeContractAsync(UInt160 callingScriptHash, UInt160 hash, string method, params StackItem[] args) { ExecutionContext context_new = CallContractInternal(hash, method, CallFlags.All, false, args); ExecutionContextState state = context_new.GetState(); @@ -259,7 +334,7 @@ internal ContractTask CallFromNativeContract(UInt160 callingScriptHash, UInt160 return task; } - internal ContractTask CallFromNativeContract(UInt160 callingScriptHash, UInt160 hash, string method, params StackItem[] args) + internal ContractTask CallFromNativeContractAsync(UInt160 callingScriptHash, UInt160 hash, string method, params StackItem[] args) { ExecutionContext context_new = CallContractInternal(hash, method, CallFlags.All, true, args); ExecutionContextState state = context_new.GetState(); @@ -269,9 +344,9 @@ internal ContractTask CallFromNativeContract(UInt160 callingScriptHash, UI return task; } - protected override void ContextUnloaded(ExecutionContext context) + internal override void UnloadContext(ExecutionContext context) { - base.ContextUnloaded(context); + base.UnloadContext(context); if (context.Script != CurrentContext?.Script) { ExecutionContextState state = context.GetState(); @@ -314,16 +389,19 @@ protected override void ContextUnloaded(ExecutionContext context) /// The snapshot used by the engine during execution. /// The block being persisted. It should be if the is . /// The used by the engine. - /// The maximum gas used in this execution. The execution will fail when the gas is exhausted. + /// The maximum gas used in this execution, in the unit of datoshi. The execution will fail when the gas is exhausted. /// The diagnostic to be used by the . /// The engine instance created. public static ApplicationEngine Create(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock = null, ProtocolSettings settings = null, long gas = TestModeGas, IDiagnostic diagnostic = null) { - return Provider?.Create(trigger, container, snapshot, persistingBlock, settings, gas, diagnostic) - ?? new ApplicationEngine(trigger, container, snapshot, persistingBlock, settings, gas, diagnostic); + // Adjust jump table according persistingBlock + var jumpTable = ApplicationEngine.DefaultJumpTable; + + return Provider?.Create(trigger, container, snapshot, persistingBlock, settings, gas, diagnostic, jumpTable) + ?? new ApplicationEngine(trigger, container, snapshot, persistingBlock, settings, gas, diagnostic, jumpTable); } - protected override void LoadContext(ExecutionContext context) + public override void LoadContext(ExecutionContext context) { // Set default execution context state var state = context.GetState(); @@ -360,7 +438,7 @@ public ExecutionContext LoadContract(ContractState contract, ContractMethodDescr }); // Call initialization - var init = contract.Manifest.Abi.GetMethod("_initialize", 0); + var init = contract.Manifest.Abi.GetMethod(ContractBasicMethod.Initialize, ContractBasicMethod.InitializePCount); if (init != null) { LoadContext(context.Clone(init.Offset)); @@ -390,21 +468,6 @@ public ExecutionContext LoadScript(Script script, int rvcount = -1, int initialP return context; } - protected override ExecutionContext LoadToken(ushort tokenId) - { - ValidateCallFlags(CallFlags.ReadStates | CallFlags.AllowCall); - ContractState contract = CurrentContext.GetState().Contract; - if (contract is null || tokenId >= contract.Nef.Tokens.Length) - throw new InvalidOperationException(); - MethodToken token = contract.Nef.Tokens[tokenId]; - if (token.ParametersCount > CurrentContext.EvaluationStack.Count) - throw new InvalidOperationException(); - StackItem[] args = new StackItem[token.ParametersCount]; - for (int i = 0; i < token.ParametersCount; i++) - args[i] = Pop(); - return CallContractInternal(token.Hash, token.Method, token.CallFlags, token.HasReturnValue, args); - } - /// /// Converts an to a that used in the virtual machine. /// @@ -502,11 +565,6 @@ internal protected void ValidateCallFlags(CallFlags requiredCallFlags) throw new InvalidOperationException($"Cannot call this SYSCALL with the flag {state.CallFlags}."); } - protected override void OnSysCall(uint method) - { - OnSysCall(services[method]); - } - /// /// Invokes the specified interoperable service. /// @@ -514,7 +572,7 @@ protected override void OnSysCall(uint method) protected virtual void OnSysCall(InteropDescriptor descriptor) { ValidateCallFlags(descriptor.RequiredCallFlags); - AddGas(descriptor.FixedPrice * ExecFeeFactor); + AddFee(descriptor.FixedPrice * ExecFeeFactor); object[] parameters = new object[descriptor.Parameters.Count]; for (int i = 0; i < parameters.Length; i++) @@ -528,7 +586,7 @@ protected virtual void OnSysCall(InteropDescriptor descriptor) protected override void PreExecuteInstruction(Instruction instruction) { Diagnostic?.PreExecuteInstruction(instruction); - AddGas(ExecFeeFactor * OpCodePrices[instruction.OpCode]); + AddFee(ExecFeeFactor * OpCodePriceTable[(byte)instruction.OpCode]); } protected override void PostExecuteInstruction(Instruction instruction) @@ -586,7 +644,7 @@ private static InteropDescriptor Register(string name, string handler, long fixe /// The block being persisted. /// The used by the engine. /// The initial position of the instruction pointer. - /// The maximum gas used in this execution. The execution will fail when the gas is exhausted. + /// The maximum gas, in the unit of datoshi, used in this execution. The execution will fail when the gas is exhausted. /// The diagnostic to be used by the . /// The engine instance created. public static ApplicationEngine Run(ReadOnlyMemory script, DataCache snapshot, IVerifiable container = null, Block persistingBlock = null, ProtocolSettings settings = null, int offset = 0, long gas = TestModeGas, IDiagnostic diagnostic = null) @@ -605,6 +663,25 @@ public T GetState() return (T)state; } + public T GetState(Func factory) + { + if (states is null) + { + T state = factory(); + SetState(state); + return state; + } + else + { + if (!states.TryGetValue(typeof(T), out object state)) + { + state = factory(); + SetState(state); + } + return (T)state; + } + } + public void SetState(T state) { states ??= new Dictionary(); @@ -613,28 +690,11 @@ public void SetState(T state) public bool IsHardforkEnabled(Hardfork hardfork) { - // Return true if there's no specific configuration or PersistingBlock is null - if (PersistingBlock is null || ProtocolSettings.Hardforks.Count == 0) - return true; - - // If the hardfork isn't specified in the configuration, check if it's a new one. - if (!ProtocolSettings.Hardforks.ContainsKey(hardfork)) - { - int currentHardforkIndex = AllHardforks.IndexOf(hardfork); - int lastConfiguredHardforkIndex = AllHardforks.IndexOf(ProtocolSettings.Hardforks.Keys.Last()); + // Return true if PersistingBlock is null and Hardfork is enabled + if (PersistingBlock is null) + return ProtocolSettings.Hardforks.ContainsKey(hardfork); - // If it's a newer hardfork compared to the ones in the configuration, disable it. - if (currentHardforkIndex > lastConfiguredHardforkIndex) - return false; - } - - if (ProtocolSettings.Hardforks.TryGetValue(hardfork, out uint height)) - { - // If the hardfork has a specific height in the configuration, check the block height. - return PersistingBlock.Index >= height; - } - // If no specific conditions are met, return true. - return true; + return ProtocolSettings.IsHardforkEnabled(hardfork, PersistingBlock.Index); } } } diff --git a/src/Neo/SmartContract/BinarySerializer.cs b/src/Neo/SmartContract/BinarySerializer.cs index d881762f84..2bdeced678 100644 --- a/src/Neo/SmartContract/BinarySerializer.cs +++ b/src/Neo/SmartContract/BinarySerializer.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// BinarySerializer.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -55,7 +56,7 @@ public ContainerPlaceholder(StackItemType type, int count) public static StackItem Deserialize(ReadOnlyMemory data, ExecutionEngineLimits limits, ReferenceCounter referenceCounter = null) { MemoryReader reader = new(data); - return Deserialize(ref reader, limits with { MaxItemSize = (uint)data.Length }, referenceCounter); + return Deserialize(ref reader, (uint)Math.Min(data.Length, limits.MaxItemSize), limits.MaxStackSize, referenceCounter); } /// @@ -65,7 +66,20 @@ public static StackItem Deserialize(ReadOnlyMemory data, ExecutionEngineLi /// The limits for the deserialization. /// The used by the . /// The deserialized . - public static StackItem Deserialize(ref MemoryReader reader, ExecutionEngineLimits limits, ReferenceCounter referenceCounter) + public static StackItem Deserialize(ref MemoryReader reader, ExecutionEngineLimits limits, ReferenceCounter referenceCounter = null) + { + return Deserialize(ref reader, limits.MaxItemSize, limits.MaxStackSize, referenceCounter); + } + + /// + /// Deserializes a from . + /// + /// The for reading data. + /// The maximum size of the result. + /// The max of items to serialize + /// The used by the . + /// The deserialized . + public static StackItem Deserialize(ref MemoryReader reader, uint maxSize, uint maxItems, ReferenceCounter referenceCounter = null) { Stack deserialized = new(); int undeserialized = 1; @@ -84,23 +98,23 @@ public static StackItem Deserialize(ref MemoryReader reader, ExecutionEngineLimi deserialized.Push(new BigInteger(reader.ReadVarMemory(Integer.MaxSize).Span)); break; case StackItemType.ByteString: - deserialized.Push(reader.ReadVarMemory((int)limits.MaxItemSize)); + deserialized.Push(reader.ReadVarMemory((int)maxSize)); break; case StackItemType.Buffer: - ReadOnlyMemory memory = reader.ReadVarMemory((int)limits.MaxItemSize); + ReadOnlyMemory memory = reader.ReadVarMemory((int)maxSize); deserialized.Push(new Buffer(memory.Span)); break; case StackItemType.Array: case StackItemType.Struct: { - int count = (int)reader.ReadVarInt(limits.MaxStackSize); + int count = (int)reader.ReadVarInt(maxItems); deserialized.Push(new ContainerPlaceholder(type, count)); undeserialized += count; } break; case StackItemType.Map: { - int count = (int)reader.ReadVarInt(limits.MaxStackSize); + int count = (int)reader.ReadVarInt(maxItems); deserialized.Push(new ContainerPlaceholder(type, count)); undeserialized += count * 2; } @@ -108,7 +122,8 @@ public static StackItem Deserialize(ref MemoryReader reader, ExecutionEngineLimi default: throw new FormatException(); } - if (deserialized.Count > limits.MaxStackSize) throw new FormatException(); + if (deserialized.Count > maxItems) + throw new FormatException(); } Stack stack_temp = new(); while (deserialized.Count > 0) @@ -147,17 +162,29 @@ public static StackItem Deserialize(ref MemoryReader reader, ExecutionEngineLimi return stack_temp.Peek(); } + /// + /// Serializes a to byte array. + /// + /// The to be serialized. + /// The used to ensure the limits. + /// The serialized byte array. + public static byte[] Serialize(StackItem item, ExecutionEngineLimits limits) + { + return Serialize(item, limits.MaxItemSize, limits.MaxStackSize); + } + /// /// Serializes a to byte array. /// /// The to be serialized. /// The maximum size of the result. + /// The max of items to serialize /// The serialized byte array. - public static byte[] Serialize(StackItem item, uint maxSize) + public static byte[] Serialize(StackItem item, long maxSize, long maxItems) { using MemoryStream ms = new(); using BinaryWriter writer = new(ms, Utility.StrictUTF8, true); - Serialize(writer, item, maxSize); + Serialize(writer, item, maxSize, maxItems); writer.Flush(); return ms.ToArray(); } @@ -168,13 +195,16 @@ public static byte[] Serialize(StackItem item, uint maxSize) /// The for writing data. /// The to be serialized. /// The maximum size of the result. - public static void Serialize(BinaryWriter writer, StackItem item, uint maxSize) + /// The max of items to serialize + public static void Serialize(BinaryWriter writer, StackItem item, long maxSize, long maxItems) { HashSet serialized = new(ReferenceEqualityComparer.Instance); Stack unserialized = new(); unserialized.Push(item); while (unserialized.Count > 0) { + if (--maxItems < 0) + throw new FormatException(); item = unserialized.Pop(); writer.Write((byte)item.Type); switch (item) diff --git a/src/Neo/SmartContract/CallFlags.cs b/src/Neo/SmartContract/CallFlags.cs index b79aa8fa80..eb0d0de92d 100644 --- a/src/Neo/SmartContract/CallFlags.cs +++ b/src/Neo/SmartContract/CallFlags.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// CallFlags.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/SmartContract/Contract.cs b/src/Neo/SmartContract/Contract.cs index 6396d3a5a2..dc60c42d50 100644 --- a/src/Neo/SmartContract/Contract.cs +++ b/src/Neo/SmartContract/Contract.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// Contract.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/SmartContract/ContractBasicMethod.cs b/src/Neo/SmartContract/ContractBasicMethod.cs new file mode 100644 index 0000000000..7897f50c0a --- /dev/null +++ b/src/Neo/SmartContract/ContractBasicMethod.cs @@ -0,0 +1,122 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractBasicMethod.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.SmartContract +{ + /// + /// This class provides a guideline for basic methods used in the Neo blockchain, offering + /// a generalized interaction mechanism for smart contract deployment, verification, updates, and destruction. + /// + public record ContractBasicMethod + { + /// + /// The verification method. This must be called when withdrawing tokens from the contract. + /// If the contract address is included in the transaction signature, this method verifies the signature. + /// Example: + /// + /// public static bool Verify() => Runtime.CheckWitness(Owner); + /// + /// + /// { + /// "name": "verify", + /// "safe": false, + /// "parameters": [], + /// "returntype": "bool" + /// } + /// + /// + public static string Verify { get; } = "verify"; + + /// + /// The initialization method. Compiled into the file if any function uses the initialize statement. + /// These functions are executed first when loading the contract. + /// Example: + /// + /// private static readonly UInt160 owner = "NdUL5oDPD159KeFpD5A9zw5xNF1xLX6nLT"; + /// + /// + public static string Initialize { get; } = "_initialize"; + + /// + /// The deployment method. Automatically executed by the ContractManagement contract when a contract is first deployed or updated. + /// + /// { + /// "name": "_deploy", + /// "safe": false, + /// "parameters": [ + /// { + /// "name": "data", + /// "type": "Any" + /// }, + /// { + /// "name": "update", + /// "type": "Boolean" + /// } + /// ], + /// "returntype": "Void" + /// } + /// + /// + public static string Deploy { get; } = "_deploy"; + + /// + /// The update method. Requires or , or both, and is passed to _deploy. + /// Should verify the signer's address using SYSCALL Neo.Runtime.CheckWitness. + /// + /// { + /// "name": "update", + /// "safe": false, + /// "parameters": [ + /// { + /// "name": "nefFile", + /// "type": "ByteArray" + /// }, + /// { + /// "name": "manifest", + /// "type": "ByteArray" + /// }, + /// { + /// "name": "data", + /// "type": "Any" + /// } + /// ], + /// "returntype": "Void" + /// } + /// + /// + public static string Update { get; } = "update"; + + /// + /// The destruction method. Deletes all the storage of the contract. + /// Should verify the signer's address using SYSCALL Neo.Runtime.CheckWitness. + /// Any tokens in the contract must be transferred before destruction. + /// + /// { + /// "name": "destroy", + /// "safe": false, + /// "parameters": [], + /// "returntype": "Void" + /// } + /// + /// + public static string Destroy { get; } = "destroy"; + + /// + /// Parameter counts for the methods. + /// -1 represents the method can take arbitrary parameters. + /// + public static int VerifyPCount { get; } = -1; + public static int InitializePCount { get; } = 0; + public static int DeployPCount { get; } = 2; + public static int UpdatePCount { get; } = 3; + public static int DestroyPCount { get; } = 0; + } +} diff --git a/src/Neo/SmartContract/ContractParameter.cs b/src/Neo/SmartContract/ContractParameter.cs index ba8583934b..4abc7aa985 100644 --- a/src/Neo/SmartContract/ContractParameter.cs +++ b/src/Neo/SmartContract/ContractParameter.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractParameter.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -44,8 +45,8 @@ public ContractParameter() { } /// The type of the parameter. public ContractParameter(ContractParameterType type) { - this.Type = type; - this.Value = type switch + Type = type; + Value = type switch { ContractParameterType.Any => null, ContractParameterType.Signature => new byte[64], diff --git a/src/Neo/SmartContract/ContractParameterType.cs b/src/Neo/SmartContract/ContractParameterType.cs index 5c030d0cf3..037927a7f0 100644 --- a/src/Neo/SmartContract/ContractParameterType.cs +++ b/src/Neo/SmartContract/ContractParameterType.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractParameterType.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/SmartContract/ContractParametersContext.cs b/src/Neo/SmartContract/ContractParametersContext.cs index b042f4e263..e8bf3ceec9 100644 --- a/src/Neo/SmartContract/ContractParametersContext.cs +++ b/src/Neo/SmartContract/ContractParametersContext.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractParametersContext.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -36,16 +37,16 @@ private class ContextItem public ContextItem(Contract contract) { - this.Script = contract.Script; - this.Parameters = contract.ParameterList.Select(p => new ContractParameter { Type = p }).ToArray(); - this.Signatures = new Dictionary(); + Script = contract.Script; + Parameters = contract.ParameterList.Select(p => new ContractParameter { Type = p }).ToArray(); + Signatures = new Dictionary(); } public ContextItem(JObject json) { - this.Script = Convert.FromBase64String(json["script"].AsString()); - this.Parameters = ((JArray)json["parameters"]).Select(p => ContractParameter.FromJson((JObject)p)).ToArray(); - this.Signatures = ((JObject)json["signatures"]).Properties.Select(p => new + Script = json["script"] is JToken.Null ? null : Convert.FromBase64String(json["script"].AsString()); + Parameters = ((JArray)json["parameters"]).Select(p => ContractParameter.FromJson((JObject)p)).ToArray(); + Signatures = ((JObject)json["signatures"]).Properties.Select(p => new { PublicKey = ECPoint.Parse(p.Key, ECCurve.Secp256r1), Signature = Convert.FromBase64String(p.Value.AsString()) @@ -55,7 +56,7 @@ public ContextItem(JObject json) public JObject ToJson() { JObject json = new(); - json["script"] = Convert.ToBase64String(Script); + json["script"] = Script == null ? null : Convert.ToBase64String(Script); json["parameters"] = new JArray(Parameters.Select(p => p.ToJson())); json["signatures"] = new JObject(); foreach (var signature in Signatures) @@ -108,10 +109,10 @@ public bool Completed /// The magic number of the network. public ContractParametersContext(DataCache snapshot, IVerifiable verifiable, uint network) { - this.Verifiable = verifiable; - this.Snapshot = snapshot; - this.ContextItems = new Dictionary(); - this.Network = network; + Verifiable = verifiable; + Snapshot = snapshot; + ContextItems = new Dictionary(); + Network = network; } /// diff --git a/src/Neo/SmartContract/ContractState.cs b/src/Neo/SmartContract/ContractState.cs index 53f90cd30a..5b413c41a4 100644 --- a/src/Neo/SmartContract/ContractState.cs +++ b/src/Neo/SmartContract/ContractState.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractState.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -22,7 +23,7 @@ namespace Neo.SmartContract /// /// Represents a deployed contract. /// - public class ContractState : IInteroperable + public class ContractState : IInteroperableVerifiable { /// /// The id of the contract. @@ -68,7 +69,7 @@ IInteroperable IInteroperable.Clone() void IInteroperable.FromReplica(IInteroperable replica) { - ContractState from = (ContractState)replica; + var from = (ContractState)replica; Id = from.Id; UpdateCounter = from.UpdateCounter; Hash = from.Hash; @@ -78,11 +79,16 @@ void IInteroperable.FromReplica(IInteroperable replica) void IInteroperable.FromStackItem(StackItem stackItem) { - Array array = (Array)stackItem; + ((IInteroperableVerifiable)this).FromStackItem(stackItem, true); + } + + void IInteroperableVerifiable.FromStackItem(StackItem stackItem, bool verify) + { + var array = (Array)stackItem; Id = (int)array[0].GetInteger(); UpdateCounter = (ushort)array[1].GetInteger(); Hash = new UInt160(array[2].GetSpan()); - Nef = ((ByteString)array[3]).Memory.AsSerializable(); + Nef = NefFile.Parse(((ByteString)array[3]).Memory, verify); Manifest = array[4].ToInteroperable(); } diff --git a/src/Neo/SmartContract/ContractTask.cs b/src/Neo/SmartContract/ContractTask.cs index 56cd06368c..20c26d8236 100644 --- a/src/Neo/SmartContract/ContractTask.cs +++ b/src/Neo/SmartContract/ContractTask.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractTask.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -15,7 +16,7 @@ namespace Neo.SmartContract [AsyncMethodBuilder(typeof(ContractTaskMethodBuilder))] class ContractTask { - private readonly ContractTaskAwaiter awaiter; + protected readonly ContractTaskAwaiter _awaiter; public static ContractTask CompletedTask { get; } @@ -27,23 +28,29 @@ static ContractTask() public ContractTask() { - awaiter = CreateAwaiter(); + _awaiter = CreateAwaiter(); } protected virtual ContractTaskAwaiter CreateAwaiter() => new(); - - public virtual ContractTaskAwaiter GetAwaiter() => awaiter; - + public ContractTaskAwaiter GetAwaiter() => _awaiter; public virtual object GetResult() => null; } [AsyncMethodBuilder(typeof(ContractTaskMethodBuilder<>))] class ContractTask : ContractTask { - protected override ContractTaskAwaiter CreateAwaiter() => new(); + public new static ContractTask CompletedTask { get; } - public override ContractTaskAwaiter GetAwaiter() => (ContractTaskAwaiter)base.GetAwaiter(); + static ContractTask() + { + CompletedTask = new ContractTask(); + CompletedTask.GetAwaiter().SetResult(); + } + protected override ContractTaskAwaiter CreateAwaiter() => new ContractTaskAwaiter(); +#pragma warning disable CS0108 // Member hides inherited member; missing new keyword + public ContractTaskAwaiter GetAwaiter() => (ContractTaskAwaiter)_awaiter; +#pragma warning restore CS0108 // Member hides inherited member; missing new keyword public override object GetResult() => GetAwaiter().GetResult(); } } diff --git a/src/Neo/SmartContract/ContractTaskAwaiter.cs b/src/Neo/SmartContract/ContractTaskAwaiter.cs index e3b93cca02..a0a0c9a1bb 100644 --- a/src/Neo/SmartContract/ContractTaskAwaiter.cs +++ b/src/Neo/SmartContract/ContractTaskAwaiter.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractTaskAwaiter.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/SmartContract/ContractTaskMethodBuilder.cs b/src/Neo/SmartContract/ContractTaskMethodBuilder.cs index 0234950cb7..bae1146f25 100644 --- a/src/Neo/SmartContract/ContractTaskMethodBuilder.cs +++ b/src/Neo/SmartContract/ContractTaskMethodBuilder.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractTaskMethodBuilder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -70,7 +71,7 @@ public void SetException(Exception exception) public void SetResult(T result) { - Task.GetAwaiter().SetResult(result); + ((ContractTaskAwaiter)Task.GetAwaiter()).SetResult(result); } public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) diff --git a/src/Neo/SmartContract/DeployedContract.cs b/src/Neo/SmartContract/DeployedContract.cs index 9096f95870..a3f9cfd9fb 100644 --- a/src/Neo/SmartContract/DeployedContract.cs +++ b/src/Neo/SmartContract/DeployedContract.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// DeployedContract.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -31,7 +32,7 @@ public DeployedContract(ContractState contract) Script = null; ScriptHash = contract.Hash; - ContractMethodDescriptor descriptor = contract.Manifest.Abi.GetMethod("verify", -1); + ContractMethodDescriptor descriptor = contract.Manifest.Abi.GetMethod(ContractBasicMethod.Verify, ContractBasicMethod.VerifyPCount); if (descriptor is null) throw new NotSupportedException("The smart contract haven't got verify method."); ParameterList = descriptor.Parameters.Select(u => u.Type).ToArray(); diff --git a/src/Neo/SmartContract/ExecutionContextState.cs b/src/Neo/SmartContract/ExecutionContextState.cs index 5e3aa3ef42..8b61ff1197 100644 --- a/src/Neo/SmartContract/ExecutionContextState.cs +++ b/src/Neo/SmartContract/ExecutionContextState.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ExecutionContextState.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/SmartContract/FindOptions.cs b/src/Neo/SmartContract/FindOptions.cs index ab23d3bff5..28445c9142 100644 --- a/src/Neo/SmartContract/FindOptions.cs +++ b/src/Neo/SmartContract/FindOptions.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// FindOptions.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/SmartContract/Helper.cs b/src/Neo/SmartContract/Helper.cs index 73b19f9616..ae31d66a50 100644 --- a/src/Neo/SmartContract/Helper.cs +++ b/src/Neo/SmartContract/Helper.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// Helper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -30,32 +31,35 @@ public static class Helper { /// /// The maximum GAS that can be consumed when is called. + /// The unit is datoshi, 1 datoshi = 1e-8 GAS /// public const long MaxVerificationGas = 1_50000000; /// /// Calculates the verification fee for a signature address. + /// In the unit of datoshi, 1 datoshi = 1e-8 GAS /// /// The calculated cost. public static long SignatureContractCost() => - ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] * 2 + - ApplicationEngine.OpCodePrices[OpCode.SYSCALL] + + ApplicationEngine.OpCodePriceTable[(byte)OpCode.PUSHDATA1] * 2 + + ApplicationEngine.OpCodePriceTable[(byte)OpCode.SYSCALL] + ApplicationEngine.CheckSigPrice; /// /// Calculates the verification fee for a multi-signature address. + /// In the unit of datoshi, 1 datoshi = 1e-8 GAS /// /// The minimum number of correct signatures that need to be provided in order for the verification to pass. /// The number of public keys in the account. /// The calculated cost. public static long MultiSignatureContractCost(int m, int n) { - long fee = ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] * (m + n); + long fee = ApplicationEngine.OpCodePriceTable[(byte)OpCode.PUSHDATA1] * (m + n); using (ScriptBuilder sb = new()) - fee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(m).ToArray()[0]]; + fee += ApplicationEngine.OpCodePriceTable[(byte)(OpCode)sb.EmitPush(m).ToArray()[0]]; using (ScriptBuilder sb = new()) - fee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(n).ToArray()[0]]; - fee += ApplicationEngine.OpCodePrices[OpCode.SYSCALL]; + fee += ApplicationEngine.OpCodePriceTable[(byte)(OpCode)sb.EmitPush(n).ToArray()[0]]; + fee += ApplicationEngine.OpCodePriceTable[(byte)OpCode.SYSCALL]; fee += ApplicationEngine.CheckSigPrice * n; return fee; } @@ -183,7 +187,14 @@ private static bool IsMultiSigContract(ReadOnlySpan script, out int m, out { if (script.Length <= i + 35) return false; if (script[++i] != 33) return false; - points?.Add(ECPoint.DecodePoint(script.Slice(i + 1, 33), ECCurve.Secp256r1)); + try + { + points?.Add(ECPoint.DecodePoint(script.Slice(i + 1, 33), ECCurve.Secp256r1)); + } + catch (Exception) // Script may contain any data, thus exceptions are allowed on point decoding. + { + return false; + } i += 34; ++n; } @@ -277,12 +288,12 @@ public static UInt160 ToScriptHash(this ReadOnlySpan script) /// The to be verified. /// The to be used for the verification. /// The snapshot used to read data. - /// The maximum GAS that can be used. + /// The maximum GAS that can be used, in the unit of datoshi, 1 datoshi = 1e-8 GAS. /// if the is verified as valid; otherwise, . - public static bool VerifyWitnesses(this IVerifiable verifiable, ProtocolSettings settings, DataCache snapshot, long gas) + public static bool VerifyWitnesses(this IVerifiable verifiable, ProtocolSettings settings, DataCache snapshot, long datoshi) { - if (gas < 0) return false; - if (gas > MaxVerificationGas) gas = MaxVerificationGas; + if (datoshi < 0) return false; + if (datoshi > MaxVerificationGas) datoshi = MaxVerificationGas; UInt160[] hashes; try @@ -296,14 +307,14 @@ public static bool VerifyWitnesses(this IVerifiable verifiable, ProtocolSettings if (hashes.Length != verifiable.Witnesses.Length) return false; for (int i = 0; i < hashes.Length; i++) { - if (!verifiable.VerifyWitness(settings, snapshot, hashes[i], verifiable.Witnesses[i], gas, out long fee)) + if (!verifiable.VerifyWitness(settings, snapshot, hashes[i], verifiable.Witnesses[i], datoshi, out long fee)) return false; - gas -= fee; + datoshi -= fee; } return true; } - internal static bool VerifyWitness(this IVerifiable verifiable, ProtocolSettings settings, DataCache snapshot, UInt160 hash, Witness witness, long gas, out long fee) + internal static bool VerifyWitness(this IVerifiable verifiable, ProtocolSettings settings, DataCache snapshot, UInt160 hash, Witness witness, long datoshi, out long fee) { fee = 0; Script invocationScript; @@ -315,13 +326,13 @@ internal static bool VerifyWitness(this IVerifiable verifiable, ProtocolSettings { return false; } - using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, verifiable, snapshot?.CreateSnapshot(), null, settings, gas)) + using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, verifiable, snapshot?.CreateSnapshot(), null, settings, datoshi)) { if (witness.VerificationScript.Length == 0) { ContractState cs = NativeContract.ContractManagement.GetContract(snapshot, hash); if (cs is null) return false; - ContractMethodDescriptor md = cs.Manifest.Abi.GetMethod("verify", -1); + ContractMethodDescriptor md = cs.Manifest.Abi.GetMethod(ContractBasicMethod.Verify, ContractBasicMethod.VerifyPCount); if (md?.ReturnType != ContractParameterType.Boolean) return false; engine.LoadContract(cs, md, CallFlags.ReadOnly); } @@ -349,7 +360,7 @@ internal static bool VerifyWitness(this IVerifiable verifiable, ProtocolSettings if (engine.Execute() == VMState.FAULT) return false; if (!engine.ResultStack.Peek().GetBoolean()) return false; - fee = engine.GasConsumed; + fee = engine.FeeConsumed; } return true; } diff --git a/src/Neo/SmartContract/IApplicationEngineProvider.cs b/src/Neo/SmartContract/IApplicationEngineProvider.cs index cc440016b7..ba7866739c 100644 --- a/src/Neo/SmartContract/IApplicationEngineProvider.cs +++ b/src/Neo/SmartContract/IApplicationEngineProvider.cs @@ -1,15 +1,17 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// IApplicationEngineProvider.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. using Neo.Network.P2P.Payloads; using Neo.Persistence; +using Neo.VM; namespace Neo.SmartContract { @@ -28,7 +30,8 @@ public interface IApplicationEngineProvider /// The used by the engine. /// The maximum gas used in this execution. The execution will fail when the gas is exhausted. /// The diagnostic to be used by the . + /// The jump table to be used by the . /// The engine instance created. - ApplicationEngine Create(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock, ProtocolSettings settings, long gas, IDiagnostic diagnostic); + ApplicationEngine Create(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock, ProtocolSettings settings, long gas, IDiagnostic diagnostic, JumpTable jumpTable); } } diff --git a/src/Neo/SmartContract/IDiagnostic.cs b/src/Neo/SmartContract/IDiagnostic.cs index 9fee923b8f..4c888b805a 100644 --- a/src/Neo/SmartContract/IDiagnostic.cs +++ b/src/Neo/SmartContract/IDiagnostic.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// IDiagnostic.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/SmartContract/IInteroperable.cs b/src/Neo/SmartContract/IInteroperable.cs index 11be45a118..2810d40d01 100644 --- a/src/Neo/SmartContract/IInteroperable.cs +++ b/src/Neo/SmartContract/IInteroperable.cs @@ -1,16 +1,17 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// IInteroperable.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. using Neo.VM; using Neo.VM.Types; -using System.Runtime.Serialization; +using System; namespace Neo.SmartContract { @@ -34,7 +35,7 @@ public interface IInteroperable public IInteroperable Clone() { - IInteroperable result = (IInteroperable)FormatterServices.GetUninitializedObject(GetType()); + var result = (IInteroperable)Activator.CreateInstance(GetType()); result.FromStackItem(ToStackItem(null)); return result; } diff --git a/src/Neo/SmartContract/IInteroperableVerifiable.cs b/src/Neo/SmartContract/IInteroperableVerifiable.cs new file mode 100644 index 0000000000..c8af7b8a5e --- /dev/null +++ b/src/Neo/SmartContract/IInteroperableVerifiable.cs @@ -0,0 +1,29 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// IInteroperableVerifiable.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.VM.Types; + +namespace Neo.SmartContract +{ + /// + /// Represents the object that can be converted to and from + /// and allows you to specify whether a verification is required. + /// + public interface IInteroperableVerifiable : IInteroperable + { + /// + /// Convert a to the current object. + /// + /// The to convert. + /// Verify the content + void FromStackItem(StackItem stackItem, bool verify = true); + } +} diff --git a/src/Neo/SmartContract/InteropDescriptor.cs b/src/Neo/SmartContract/InteropDescriptor.cs index e6f066a1d1..29c88f46ed 100644 --- a/src/Neo/SmartContract/InteropDescriptor.cs +++ b/src/Neo/SmartContract/InteropDescriptor.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// InteropDescriptor.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/SmartContract/InteropParameterDescriptor.cs b/src/Neo/SmartContract/InteropParameterDescriptor.cs index e7cdb94893..24e103afc5 100644 --- a/src/Neo/SmartContract/InteropParameterDescriptor.cs +++ b/src/Neo/SmartContract/InteropParameterDescriptor.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// InteropParameterDescriptor.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -23,7 +24,7 @@ namespace Neo.SmartContract /// public class InteropParameterDescriptor { - private readonly ValidatorAttribute[] validators; + private readonly ValidatorAttribute[] _validators; /// /// The name of the parameter. @@ -79,15 +80,15 @@ public class InteropParameterDescriptor }; internal InteropParameterDescriptor(ParameterInfo parameterInfo) - : this(parameterInfo.ParameterType) + : this(parameterInfo.ParameterType, parameterInfo.GetCustomAttributes(true).ToArray()) { - this.Name = parameterInfo.Name; - this.validators = parameterInfo.GetCustomAttributes(true).ToArray(); + Name = parameterInfo.Name; } - internal InteropParameterDescriptor(Type type) + internal InteropParameterDescriptor(Type type, params ValidatorAttribute[] validators) { - this.Type = type; + Type = type; + _validators = validators; if (IsEnum) { Converter = converters[type.GetEnumUnderlyingType()]; @@ -108,7 +109,7 @@ internal InteropParameterDescriptor(Type type) public void Validate(StackItem item) { - foreach (ValidatorAttribute validator in validators) + foreach (var validator in _validators) validator.Validate(item); } } diff --git a/src/Neo/SmartContract/Iterators/IIterator.cs b/src/Neo/SmartContract/Iterators/IIterator.cs index dc576e735d..78c42f1abd 100644 --- a/src/Neo/SmartContract/Iterators/IIterator.cs +++ b/src/Neo/SmartContract/Iterators/IIterator.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// IIterator.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/SmartContract/Iterators/StorageIterator.cs b/src/Neo/SmartContract/Iterators/StorageIterator.cs index 08f6b13643..397333ad7c 100644 --- a/src/Neo/SmartContract/Iterators/StorageIterator.cs +++ b/src/Neo/SmartContract/Iterators/StorageIterator.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// StorageIterator.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/SmartContract/JsonSerializer.cs b/src/Neo/SmartContract/JsonSerializer.cs index 06eb1d5c22..4d77104a6e 100644 --- a/src/Neo/SmartContract/JsonSerializer.cs +++ b/src/Neo/SmartContract/JsonSerializer.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// JsonSerializer.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/SmartContract/KeyBuilder.cs b/src/Neo/SmartContract/KeyBuilder.cs index 617c8879d1..cfd27cc6f8 100644 --- a/src/Neo/SmartContract/KeyBuilder.cs +++ b/src/Neo/SmartContract/KeyBuilder.cs @@ -1,15 +1,17 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// KeyBuilder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. using Neo.IO; using System; +using System.Buffers.Binary; using System.IO; namespace Neo.SmartContract @@ -28,8 +30,22 @@ public class KeyBuilder /// The prefix of the key. public KeyBuilder(int id, byte prefix) { - Add(id); - this.stream.WriteByte(prefix); + var data = new byte[sizeof(int)]; + BinaryPrimitives.WriteInt32LittleEndian(data, id); + + stream.Write(data); + stream.WriteByte(prefix); + } + + /// + /// Adds part of the key to the builder. + /// + /// Part of the key. + /// A reference to this instance after the add operation has completed. + public KeyBuilder Add(byte key) + { + stream.WriteByte(key); + return this; } /// @@ -59,28 +75,55 @@ public KeyBuilder Add(ISerializable key) } /// - /// Adds part of the key to the builder. + /// Adds part of the key to the builder in BigEndian. /// - /// The type of the parameter. /// Part of the key. /// A reference to this instance after the add operation has completed. - unsafe public KeyBuilder Add(T key) where T : unmanaged + public KeyBuilder AddBigEndian(int key) { - return Add(new ReadOnlySpan(&key, sizeof(T))); + var data = new byte[sizeof(int)]; + BinaryPrimitives.WriteInt32BigEndian(data, key); + + return Add(data); } /// - /// Adds part of the key to the builder with big-endian. + /// Adds part of the key to the builder in BigEndian. /// - /// The type of the parameter. /// Part of the key. /// A reference to this instance after the add operation has completed. - unsafe public KeyBuilder AddBigEndian(T key) where T : unmanaged + public KeyBuilder AddBigEndian(uint key) { - ReadOnlySpan buffer = new(&key, sizeof(T)); - for (int i = buffer.Length - 1; i >= 0; i--) - stream.WriteByte(buffer[i]); - return this; + var data = new byte[sizeof(uint)]; + BinaryPrimitives.WriteUInt32BigEndian(data, key); + + return Add(data); + } + + /// + /// Adds part of the key to the builder in BigEndian. + /// + /// Part of the key. + /// A reference to this instance after the add operation has completed. + public KeyBuilder AddBigEndian(long key) + { + var data = new byte[sizeof(long)]; + BinaryPrimitives.WriteInt64BigEndian(data, key); + + return Add(data); + } + + /// + /// Adds part of the key to the builder in BigEndian. + /// + /// Part of the key. + /// A reference to this instance after the add operation has completed. + public KeyBuilder AddBigEndian(ulong key) + { + var data = new byte[sizeof(ulong)]; + BinaryPrimitives.WriteUInt64BigEndian(data, key); + + return Add(data); } /// diff --git a/src/Neo/SmartContract/LogEventArgs.cs b/src/Neo/SmartContract/LogEventArgs.cs index 623b8dc3d5..fb1d656732 100644 --- a/src/Neo/SmartContract/LogEventArgs.cs +++ b/src/Neo/SmartContract/LogEventArgs.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// LogEventArgs.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -41,9 +42,9 @@ public class LogEventArgs : EventArgs /// The message of the log. public LogEventArgs(IVerifiable container, UInt160 script_hash, string message) { - this.ScriptContainer = container; - this.ScriptHash = script_hash; - this.Message = message; + ScriptContainer = container; + ScriptHash = script_hash; + Message = message; } } } diff --git a/src/Neo/SmartContract/Manifest/ContractAbi.cs b/src/Neo/SmartContract/Manifest/ContractAbi.cs index 709668391c..3660d1a99e 100644 --- a/src/Neo/SmartContract/Manifest/ContractAbi.cs +++ b/src/Neo/SmartContract/Manifest/ContractAbi.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractAbi.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/SmartContract/Manifest/ContractEventDescriptor.cs b/src/Neo/SmartContract/Manifest/ContractEventDescriptor.cs index 1b6bd24fe7..90227dd78d 100644 --- a/src/Neo/SmartContract/Manifest/ContractEventDescriptor.cs +++ b/src/Neo/SmartContract/Manifest/ContractEventDescriptor.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractEventDescriptor.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/SmartContract/Manifest/ContractGroup.cs b/src/Neo/SmartContract/Manifest/ContractGroup.cs index ebb0dbd12c..231354327c 100644 --- a/src/Neo/SmartContract/Manifest/ContractGroup.cs +++ b/src/Neo/SmartContract/Manifest/ContractGroup.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractGroup.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/SmartContract/Manifest/ContractManifest.cs b/src/Neo/SmartContract/Manifest/ContractManifest.cs index e68c764800..078e8fd35e 100644 --- a/src/Neo/SmartContract/Manifest/ContractManifest.cs +++ b/src/Neo/SmartContract/Manifest/ContractManifest.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractManifest.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -81,7 +82,7 @@ void IInteroperable.FromStackItem(StackItem stackItem) { Null _ => WildcardContainer.CreateWildcard(), // Array array when array.Any(p => ((ByteString)p).Size == 0) => WildcardContainer.CreateWildcard(), - Array array => WildcardContainer.Create(array.Select(p => new ContractPermissionDescriptor(p.GetSpan())).ToArray()), + Array array => WildcardContainer.Create(array.Select(ContractPermissionDescriptor.Create).ToArray()), _ => throw new ArgumentException(null, nameof(stackItem)) }; Extra = (JObject)JToken.Parse(@struct[7].GetSpan()); @@ -172,10 +173,21 @@ public JObject ToJson() /// /// Determines whether the manifest is valid. /// + /// The used for test serialization. /// The hash of the contract. /// if the manifest is valid; otherwise, . - public bool IsValid(UInt160 hash) + public bool IsValid(ExecutionEngineLimits limits, UInt160 hash) { + // Ensure that is serializable + try + { + _ = BinarySerializer.Serialize(ToStackItem(null), limits); + } + catch + { + return false; + } + // Check groups return Groups.All(u => u.IsValid(hash)); } } diff --git a/src/Neo/SmartContract/Manifest/ContractMethodDescriptor.cs b/src/Neo/SmartContract/Manifest/ContractMethodDescriptor.cs index 45041d54b3..cc8d0a16d5 100644 --- a/src/Neo/SmartContract/Manifest/ContractMethodDescriptor.cs +++ b/src/Neo/SmartContract/Manifest/ContractMethodDescriptor.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractMethodDescriptor.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -72,7 +73,7 @@ public override StackItem ToStackItem(ReferenceCounter referenceCounter) }; if (string.IsNullOrEmpty(descriptor.Name)) throw new FormatException(); _ = descriptor.Parameters.ToDictionary(p => p.Name); - if (!Enum.IsDefined(descriptor.ReturnType)) throw new FormatException(); + if (!Enum.IsDefined(typeof(ContractParameterType), descriptor.ReturnType)) throw new FormatException(); if (descriptor.Offset < 0) throw new FormatException(); return descriptor; } diff --git a/src/Neo/SmartContract/Manifest/ContractParameterDefinition.cs b/src/Neo/SmartContract/Manifest/ContractParameterDefinition.cs index f54abd8c90..61906558d0 100644 --- a/src/Neo/SmartContract/Manifest/ContractParameterDefinition.cs +++ b/src/Neo/SmartContract/Manifest/ContractParameterDefinition.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractParameterDefinition.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -56,7 +57,7 @@ public static ContractParameterDefinition FromJson(JObject json) }; if (string.IsNullOrEmpty(parameter.Name)) throw new FormatException(); - if (!Enum.IsDefined(parameter.Type) || parameter.Type == ContractParameterType.Void) + if (!Enum.IsDefined(typeof(ContractParameterType), parameter.Type) || parameter.Type == ContractParameterType.Void) throw new FormatException(); return parameter; } diff --git a/src/Neo/SmartContract/Manifest/ContractPermission.cs b/src/Neo/SmartContract/Manifest/ContractPermission.cs index a99c63c52b..44c67fd9a1 100644 --- a/src/Neo/SmartContract/Manifest/ContractPermission.cs +++ b/src/Neo/SmartContract/Manifest/ContractPermission.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractPermission.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/SmartContract/Manifest/ContractPermissionDescriptor.cs b/src/Neo/SmartContract/Manifest/ContractPermissionDescriptor.cs index 133c338c6c..f1f22d99d8 100644 --- a/src/Neo/SmartContract/Manifest/ContractPermissionDescriptor.cs +++ b/src/Neo/SmartContract/Manifest/ContractPermissionDescriptor.cs @@ -1,16 +1,18 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractPermissionDescriptor.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. using Neo.Cryptography.ECC; using Neo.IO; using Neo.Json; +using Neo.VM.Types; using System; namespace Neo.SmartContract.Manifest @@ -47,8 +49,8 @@ public class ContractPermissionDescriptor : IEquatable span) @@ -66,6 +68,11 @@ internal ContractPermissionDescriptor(ReadOnlySpan span) } } + public static ContractPermissionDescriptor Create(StackItem item) + { + return item.Equals(StackItem.Null) ? CreateWildcard() : new ContractPermissionDescriptor(item.GetSpan()); + } + /// /// Creates a new instance of the class with the specified contract hash. /// diff --git a/src/Neo/SmartContract/Manifest/WildCardContainer.cs b/src/Neo/SmartContract/Manifest/WildCardContainer.cs index 847090cbb1..3002042d04 100644 --- a/src/Neo/SmartContract/Manifest/WildCardContainer.cs +++ b/src/Neo/SmartContract/Manifest/WildCardContainer.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// WildCardContainer.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/SmartContract/MaxLengthAttribute.cs b/src/Neo/SmartContract/MaxLengthAttribute.cs index 73e46b3cf4..9ab982e5e2 100644 --- a/src/Neo/SmartContract/MaxLengthAttribute.cs +++ b/src/Neo/SmartContract/MaxLengthAttribute.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// MaxLengthAttribute.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/SmartContract/MethodToken.cs b/src/Neo/SmartContract/MethodToken.cs index 09ed7b1eac..1b391edd32 100644 --- a/src/Neo/SmartContract/MethodToken.cs +++ b/src/Neo/SmartContract/MethodToken.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// MethodToken.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/SmartContract/Native/AccountState.cs b/src/Neo/SmartContract/Native/AccountState.cs index dc38d6f1fc..031b37f8eb 100644 --- a/src/Neo/SmartContract/Native/AccountState.cs +++ b/src/Neo/SmartContract/Native/AccountState.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// AccountState.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/SmartContract/Native/ContractEventAttribute.cs b/src/Neo/SmartContract/Native/ContractEventAttribute.cs new file mode 100644 index 0000000000..656ecef725 --- /dev/null +++ b/src/Neo/SmartContract/Native/ContractEventAttribute.cs @@ -0,0 +1,165 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractEventAttribute.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.SmartContract.Manifest; +using System; +using System.Diagnostics; + +namespace Neo.SmartContract.Native +{ + [DebuggerDisplay("{Descriptor.Name}")] + [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = true)] + internal class ContractEventAttribute : Attribute + { + public int Order { get; init; } + public ContractEventDescriptor Descriptor { get; set; } + public Hardfork? ActiveIn { get; init; } = null; + + public ContractEventAttribute(Hardfork activeIn, int order, string name, + string arg1Name, ContractParameterType arg1Value) : this(order, name, arg1Name, arg1Value) + { + ActiveIn = activeIn; + } + + public ContractEventAttribute(int order, string name, string arg1Name, ContractParameterType arg1Value) + { + Order = order; + Descriptor = new ContractEventDescriptor() + { + Name = name, + Parameters = new ContractParameterDefinition[] + { + new ContractParameterDefinition() + { + Name = arg1Name, + Type = arg1Value + } + } + }; + } + + public ContractEventAttribute(Hardfork activeIn, int order, string name, + string arg1Name, ContractParameterType arg1Value, + string arg2Name, ContractParameterType arg2Value) : this(order, name, arg1Name, arg1Value, arg2Name, arg2Value) + { + ActiveIn = activeIn; + } + + public ContractEventAttribute(int order, string name, + string arg1Name, ContractParameterType arg1Value, + string arg2Name, ContractParameterType arg2Value) + { + Order = order; + Descriptor = new ContractEventDescriptor() + { + Name = name, + Parameters = new ContractParameterDefinition[] + { + new ContractParameterDefinition() + { + Name = arg1Name, + Type = arg1Value + }, + new ContractParameterDefinition() + { + Name = arg2Name, + Type = arg2Value + } + } + }; + } + + public ContractEventAttribute(Hardfork activeIn, int order, string name, + string arg1Name, ContractParameterType arg1Value, + string arg2Name, ContractParameterType arg2Value, + string arg3Name, ContractParameterType arg3Value) : this(order, name, arg1Name, arg1Value, arg2Name, arg2Value, arg3Name, arg3Value) + { + ActiveIn = activeIn; + } + + public ContractEventAttribute(int order, string name, + string arg1Name, ContractParameterType arg1Value, + string arg2Name, ContractParameterType arg2Value, + string arg3Name, ContractParameterType arg3Value + ) + { + Order = order; + Descriptor = new ContractEventDescriptor() + { + Name = name, + Parameters = new ContractParameterDefinition[] + { + new ContractParameterDefinition() + { + Name = arg1Name, + Type = arg1Value + }, + new ContractParameterDefinition() + { + Name = arg2Name, + Type = arg2Value + }, + new ContractParameterDefinition() + { + Name = arg3Name, + Type = arg3Value + } + } + }; + } + + public ContractEventAttribute(Hardfork activeIn, int order, string name, + string arg1Name, ContractParameterType arg1Value, + string arg2Name, ContractParameterType arg2Value, + string arg3Name, ContractParameterType arg3Value, + string arg4Name, ContractParameterType arg4Value) : this(order, name, arg1Name, arg1Value, arg2Name, arg2Value, arg3Name, arg3Value, arg4Name, arg4Value) + { + ActiveIn = activeIn; + } + + public ContractEventAttribute(int order, string name, + string arg1Name, ContractParameterType arg1Value, + string arg2Name, ContractParameterType arg2Value, + string arg3Name, ContractParameterType arg3Value, + string arg4Name, ContractParameterType arg4Value + ) + { + Order = order; + Descriptor = new ContractEventDescriptor() + { + Name = name, + Parameters = new ContractParameterDefinition[] + { + new ContractParameterDefinition() + { + Name = arg1Name, + Type = arg1Value + }, + new ContractParameterDefinition() + { + Name = arg2Name, + Type = arg2Value + }, + new ContractParameterDefinition() + { + Name = arg3Name, + Type = arg3Value + }, + new ContractParameterDefinition() + { + Name = arg4Name, + Type = arg4Value + } + } + }; + } + } +} diff --git a/src/Neo/SmartContract/Native/ContractManagement.cs b/src/Neo/SmartContract/Native/ContractManagement.cs index f7ed44542d..c533f029a6 100644 --- a/src/Neo/SmartContract/Native/ContractManagement.cs +++ b/src/Neo/SmartContract/Native/ContractManagement.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractManagement.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -34,50 +35,10 @@ public sealed class ContractManagement : NativeContract private const byte Prefix_Contract = 8; private const byte Prefix_ContractHash = 12; - internal ContractManagement() - { - var events = new List(Manifest.Abi.Events) - { - new ContractEventDescriptor - { - Name = "Deploy", - Parameters = new ContractParameterDefinition[] - { - new ContractParameterDefinition() - { - Name = "Hash", - Type = ContractParameterType.Hash160 - } - } - }, - new ContractEventDescriptor - { - Name = "Update", - Parameters = new ContractParameterDefinition[] - { - new ContractParameterDefinition() - { - Name = "Hash", - Type = ContractParameterType.Hash160 - } - } - }, - new ContractEventDescriptor - { - Name = "Destroy", - Parameters = new ContractParameterDefinition[] - { - new ContractParameterDefinition() - { - Name = "Hash", - Type = ContractParameterType.Hash160 - } - } - } - }; - - Manifest.Abi.Events = events.ToArray(); - } + [ContractEvent(0, name: "Deploy", "Hash", ContractParameterType.Hash160)] + [ContractEvent(1, name: "Update", "Hash", ContractParameterType.Hash160)] + [ContractEvent(2, name: "Destroy", "Hash", ContractParameterType.Hash160)] + internal ContractManagement() : base() { } private int GetNextAvailableId(DataCache snapshot) { @@ -87,48 +48,82 @@ private int GetNextAvailableId(DataCache snapshot) return value; } - internal override ContractTask Initialize(ApplicationEngine engine) + internal override ContractTask InitializeAsync(ApplicationEngine engine, Hardfork? hardfork) { - engine.Snapshot.Add(CreateStorageKey(Prefix_MinimumDeploymentFee), new StorageItem(10_00000000)); - engine.Snapshot.Add(CreateStorageKey(Prefix_NextAvailableId), new StorageItem(1)); + if (hardfork == ActiveIn) + { + engine.Snapshot.Add(CreateStorageKey(Prefix_MinimumDeploymentFee), new StorageItem(10_00000000)); + engine.Snapshot.Add(CreateStorageKey(Prefix_NextAvailableId), new StorageItem(1)); + } return ContractTask.CompletedTask; } - private async ContractTask OnDeploy(ApplicationEngine engine, ContractState contract, StackItem data, bool update) + private async ContractTask OnDeployAsync(ApplicationEngine engine, ContractState contract, StackItem data, bool update) { - ContractMethodDescriptor md = contract.Manifest.Abi.GetMethod("_deploy", 2); + ContractMethodDescriptor md = contract.Manifest.Abi.GetMethod(ContractBasicMethod.Deploy, ContractBasicMethod.DeployPCount); if (md is not null) - await engine.CallFromNativeContract(Hash, contract.Hash, md.Name, data, update); + await engine.CallFromNativeContractAsync(Hash, contract.Hash, md.Name, data, update); engine.SendNotification(Hash, update ? "Update" : "Deploy", new VM.Types.Array(engine.ReferenceCounter) { contract.Hash.ToArray() }); } - internal override async ContractTask OnPersist(ApplicationEngine engine) + internal override async ContractTask OnPersistAsync(ApplicationEngine engine) { foreach (NativeContract contract in Contracts) { - uint[] updates = engine.ProtocolSettings.NativeUpdateHistory[contract.Name]; - if (updates.Length == 0 || updates[0] != engine.PersistingBlock.Index) - continue; - engine.Snapshot.Add(CreateStorageKey(Prefix_Contract).Add(contract.Hash), new StorageItem(new ContractState + if (contract.IsInitializeBlock(engine.ProtocolSettings, engine.PersistingBlock.Index, out var hfs)) { - Id = contract.Id, - Nef = contract.Nef, - Hash = contract.Hash, - Manifest = contract.Manifest - })); - engine.Snapshot.Add(CreateStorageKey(Prefix_ContractHash).AddBigEndian(contract.Id), new StorageItem(contract.Hash.ToArray())); - await contract.Initialize(engine); + ContractState contractState = contract.GetContractState(engine.ProtocolSettings, engine.PersistingBlock.Index); + StorageItem state = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_Contract).Add(contract.Hash)); + + if (state is null) + { + // Create the contract state + engine.Snapshot.Add(CreateStorageKey(Prefix_Contract).Add(contract.Hash), new StorageItem(contractState)); + engine.Snapshot.Add(CreateStorageKey(Prefix_ContractHash).AddBigEndian(contract.Id), new StorageItem(contract.Hash.ToArray())); + + // Initialize the native smart contract if it's active starting from the genesis. + // If it's not the case, then hardfork-based initialization will be performed down below. + if (contract.ActiveIn is null) + { + await contract.InitializeAsync(engine, null); + } + } + else + { + // Parse old contract + var oldContract = state.GetInteroperable(false); + // Increase the update counter + oldContract.UpdateCounter++; + // Modify nef and manifest + oldContract.Nef = contractState.Nef; + oldContract.Manifest = contractState.Manifest; + } + + // Initialize native contract for all hardforks that are active starting from the persisting block. + // If the contract is active starting from some non-nil hardfork, then this hardfork is also included into hfs. + if (hfs?.Length > 0) + { + foreach (var hf in hfs) + { + await contract.InitializeAsync(engine, hf); + } + } + + // Emit native contract notification + engine.SendNotification(Hash, state is null ? "Deploy" : "Update", new VM.Types.Array(engine.ReferenceCounter) { contract.Hash.ToArray() }); + } } } [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] private long GetMinimumDeploymentFee(DataCache snapshot) { + // In the unit of datoshi, 1 datoshi = 1e-8 GAS return (long)(BigInteger)snapshot[CreateStorageKey(Prefix_MinimumDeploymentFee)]; } [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] - private void SetMinimumDeploymentFee(ApplicationEngine engine, BigInteger value) + private void SetMinimumDeploymentFee(ApplicationEngine engine, BigInteger value/* In the unit of datoshi, 1 datoshi = 1e-8 GAS*/) { if (value < 0) throw new ArgumentOutOfRangeException(nameof(value)); if (!CheckCommittee(engine)) throw new InvalidOperationException(); @@ -144,7 +139,7 @@ private void SetMinimumDeploymentFee(ApplicationEngine engine, BigInteger value) [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public ContractState GetContract(DataCache snapshot, UInt160 hash) { - return snapshot.TryGet(CreateStorageKey(Prefix_Contract).Add(hash))?.GetInteroperable(); + return snapshot.TryGet(CreateStorageKey(Prefix_Contract).Add(hash))?.GetInteroperable(false); } /// @@ -205,7 +200,7 @@ public bool HasMethod(DataCache snapshot, UInt160 hash, string method, int pcoun public IEnumerable ListContracts(DataCache snapshot) { byte[] listContractsPrefix = CreateStorageKey(Prefix_Contract).ToArray(); - return snapshot.Find(listContractsPrefix).Select(kvp => kvp.Value.GetInteroperable()); + return snapshot.Find(listContractsPrefix).Select(kvp => kvp.Value.GetInteroperable(false)); } [ContractMethod(RequiredCallFlags = CallFlags.All)] @@ -224,7 +219,7 @@ private async ContractTask Deploy(ApplicationEngine engine, byte[ if (manifest.Length == 0) throw new ArgumentException($"Invalid Manifest Length: {manifest.Length}"); - engine.AddGas(Math.Max( + engine.AddFee(Math.Max( engine.StoragePrice * (nefFile.Length + manifest.Length), GetMinimumDeploymentFee(engine.Snapshot) )); @@ -249,12 +244,12 @@ private async ContractTask Deploy(ApplicationEngine engine, byte[ Manifest = parsedManifest }; - if (!contract.Manifest.IsValid(hash)) throw new InvalidOperationException($"Invalid Manifest Hash: {hash}"); + if (!contract.Manifest.IsValid(engine.Limits, hash)) throw new InvalidOperationException($"Invalid Manifest: {hash}"); engine.Snapshot.Add(key, new StorageItem(contract)); engine.Snapshot.Add(CreateStorageKey(Prefix_ContractHash).AddBigEndian(contract.Id), new StorageItem(hash.ToArray())); - await OnDeploy(engine, contract, data, false); + await OnDeployAsync(engine, contract, data, false); return contract; } @@ -270,9 +265,9 @@ private ContractTask Update(ApplicationEngine engine, byte[] nefFile, byte[] man { if (nefFile is null && manifest is null) throw new ArgumentException(); - engine.AddGas(engine.StoragePrice * ((nefFile?.Length ?? 0) + (manifest?.Length ?? 0))); + engine.AddFee(engine.StoragePrice * ((nefFile?.Length ?? 0) + (manifest?.Length ?? 0))); - var contract = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_Contract).Add(engine.CallingScriptHash))?.GetInteroperable(); + var contract = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_Contract).Add(engine.CallingScriptHash))?.GetInteroperable(false); if (contract is null) throw new InvalidOperationException($"Updating Contract Does Not Exist: {engine.CallingScriptHash}"); if (contract.UpdateCounter == ushort.MaxValue) throw new InvalidOperationException($"The contract reached the maximum number of updates."); @@ -291,13 +286,13 @@ private ContractTask Update(ApplicationEngine engine, byte[] nefFile, byte[] man ContractManifest manifest_new = ContractManifest.Parse(manifest); if (manifest_new.Name != contract.Manifest.Name) throw new InvalidOperationException("The name of the contract can't be changed."); - if (!manifest_new.IsValid(contract.Hash)) - throw new InvalidOperationException($"Invalid Manifest Hash: {contract.Hash}"); + if (!manifest_new.IsValid(engine.Limits, contract.Hash)) + throw new InvalidOperationException($"Invalid Manifest: {contract.Hash}"); contract.Manifest = manifest_new; } Helper.Check(new VM.Script(contract.Nef.Script, engine.IsHardforkEnabled(Hardfork.HF_Basilisk)), contract.Manifest.Abi); contract.UpdateCounter++; // Increase update counter - return OnDeploy(engine, contract, data, true); + return OnDeployAsync(engine, contract, data, true); } [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States | CallFlags.AllowNotify)] @@ -305,7 +300,7 @@ private void Destroy(ApplicationEngine engine) { UInt160 hash = engine.CallingScriptHash; StorageKey ckey = CreateStorageKey(Prefix_Contract).Add(hash); - ContractState contract = engine.Snapshot.TryGet(ckey)?.GetInteroperable(); + ContractState contract = engine.Snapshot.TryGet(ckey)?.GetInteroperable(false); if (contract is null) return; engine.Snapshot.Delete(ckey); engine.Snapshot.Delete(CreateStorageKey(Prefix_ContractHash).AddBigEndian(contract.Id)); diff --git a/src/Neo/SmartContract/Native/ContractMethodAttribute.cs b/src/Neo/SmartContract/Native/ContractMethodAttribute.cs index c4f12ed229..7caa27c8b0 100644 --- a/src/Neo/SmartContract/Native/ContractMethodAttribute.cs +++ b/src/Neo/SmartContract/Native/ContractMethodAttribute.cs @@ -1,17 +1,20 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractMethodAttribute.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. using System; +using System.Diagnostics; namespace Neo.SmartContract.Native { + [DebuggerDisplay("{Name}")] [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = false)] internal class ContractMethodAttribute : Attribute { @@ -19,5 +22,20 @@ internal class ContractMethodAttribute : Attribute public CallFlags RequiredCallFlags { get; init; } public long CpuFee { get; init; } public long StorageFee { get; init; } + public Hardfork? ActiveIn { get; init; } = null; + public Hardfork? DeprecatedIn { get; init; } = null; + + public ContractMethodAttribute() { } + + public ContractMethodAttribute(Hardfork activeIn) + { + ActiveIn = activeIn; + } + + public ContractMethodAttribute(bool isDeprecated, Hardfork deprecatedIn) + { + if (!isDeprecated) throw new ArgumentException("isDeprecated must be true", nameof(isDeprecated)); + DeprecatedIn = deprecatedIn; + } } } diff --git a/src/Neo/SmartContract/Native/ContractMethodMetadata.cs b/src/Neo/SmartContract/Native/ContractMethodMetadata.cs index e0b342aea1..30874efb47 100644 --- a/src/Neo/SmartContract/Native/ContractMethodMetadata.cs +++ b/src/Neo/SmartContract/Native/ContractMethodMetadata.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractMethodMetadata.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -14,6 +15,7 @@ using Neo.SmartContract.Manifest; using Neo.VM.Types; using System; +using System.Diagnostics; using System.Linq; using System.Numerics; using System.Reflection; @@ -21,6 +23,7 @@ namespace Neo.SmartContract.Native { + [DebuggerDisplay("{Name}")] internal class ContractMethodMetadata { public string Name { get; } @@ -32,30 +35,34 @@ internal class ContractMethodMetadata public long StorageFee { get; } public CallFlags RequiredCallFlags { get; } public ContractMethodDescriptor Descriptor { get; } + public Hardfork? ActiveIn { get; init; } = null; + public Hardfork? DeprecatedIn { get; init; } = null; public ContractMethodMetadata(MemberInfo member, ContractMethodAttribute attribute) { - this.Name = attribute.Name ?? member.Name.ToLower()[0] + member.Name[1..]; - this.Handler = member switch + Name = attribute.Name ?? member.Name.ToLower()[0] + member.Name[1..]; + Handler = member switch { MethodInfo m => m, PropertyInfo p => p.GetMethod, _ => throw new ArgumentException(null, nameof(member)) }; - ParameterInfo[] parameterInfos = this.Handler.GetParameters(); + ParameterInfo[] parameterInfos = Handler.GetParameters(); if (parameterInfos.Length > 0) { NeedApplicationEngine = parameterInfos[0].ParameterType.IsAssignableFrom(typeof(ApplicationEngine)); NeedSnapshot = parameterInfos[0].ParameterType.IsAssignableFrom(typeof(DataCache)); } if (NeedApplicationEngine || NeedSnapshot) - this.Parameters = parameterInfos.Skip(1).Select(p => new InteropParameterDescriptor(p)).ToArray(); + Parameters = parameterInfos.Skip(1).Select(p => new InteropParameterDescriptor(p)).ToArray(); else - this.Parameters = parameterInfos.Select(p => new InteropParameterDescriptor(p)).ToArray(); - this.CpuFee = attribute.CpuFee; - this.StorageFee = attribute.StorageFee; - this.RequiredCallFlags = attribute.RequiredCallFlags; - this.Descriptor = new ContractMethodDescriptor + Parameters = parameterInfos.Select(p => new InteropParameterDescriptor(p)).ToArray(); + CpuFee = attribute.CpuFee; + StorageFee = attribute.StorageFee; + RequiredCallFlags = attribute.RequiredCallFlags; + ActiveIn = attribute.ActiveIn; + DeprecatedIn = attribute.DeprecatedIn; + Descriptor = new ContractMethodDescriptor { Name = Name, ReturnType = ToParameterType(Handler.ReturnType), diff --git a/src/Neo/SmartContract/Native/CryptoLib.BLS12_381.cs b/src/Neo/SmartContract/Native/CryptoLib.BLS12_381.cs index 28e1c063b7..f966a73fd1 100644 --- a/src/Neo/SmartContract/Native/CryptoLib.BLS12_381.cs +++ b/src/Neo/SmartContract/Native/CryptoLib.BLS12_381.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// CryptoLib.BLS12_381.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Neo.Cryptography.BLS12_381; using Neo.VM.Types; using System; diff --git a/src/Neo/SmartContract/Native/CryptoLib.cs b/src/Neo/SmartContract/Native/CryptoLib.cs index 16ec960a27..9027298752 100644 --- a/src/Neo/SmartContract/Native/CryptoLib.cs +++ b/src/Neo/SmartContract/Native/CryptoLib.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// CryptoLib.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -20,13 +21,15 @@ namespace Neo.SmartContract.Native /// public sealed partial class CryptoLib : NativeContract { - private static readonly Dictionary curves = new() + private static readonly Dictionary s_curves = new() { - [NamedCurve.secp256k1] = ECCurve.Secp256k1, - [NamedCurve.secp256r1] = ECCurve.Secp256r1 + [NamedCurveHash.secp256k1SHA256] = (ECCurve.Secp256k1, Hasher.SHA256), + [NamedCurveHash.secp256r1SHA256] = (ECCurve.Secp256r1, Hasher.SHA256), + [NamedCurveHash.secp256k1Keccak256] = (ECCurve.Secp256k1, Hasher.Keccak256), + [NamedCurveHash.secp256r1Keccak256] = (ECCurve.Secp256r1, Hasher.Keccak256), }; - internal CryptoLib() { } + internal CryptoLib() : base() { } /// /// Computes the hash value for the specified byte array using the ripemd160 algorithm. @@ -63,20 +66,49 @@ public static byte[] Murmur32(byte[] data, uint seed) return murmur.ComputeHash(data); } + /// + /// Computes the hash value for the specified byte array using the keccak256 algorithm. + /// + /// The input to compute the hash code for. + /// Computed hash + [ContractMethod(Hardfork.HF_Cockatrice, CpuFee = 1 << 15)] + public static byte[] Keccak256(byte[] data) + { + return data.Keccak256(); + } + /// /// Verifies that a digital signature is appropriate for the provided key and message using the ECDSA algorithm. /// /// The signed message. /// The public key to be used. /// The signature to be verified. - /// The curve to be used by the ECDSA algorithm. + /// A pair of the curve to be used by the ECDSA algorithm and the hasher function to be used to hash message. /// if the signature is valid; otherwise, . - [ContractMethod(CpuFee = 1 << 15)] - public static bool VerifyWithECDsa(byte[] message, byte[] pubkey, byte[] signature, NamedCurve curve) + [ContractMethod(Hardfork.HF_Cockatrice, CpuFee = 1 << 15)] + public static bool VerifyWithECDsa(byte[] message, byte[] pubkey, byte[] signature, NamedCurveHash curveHash) + { + try + { + var ch = s_curves[curveHash]; + return Crypto.VerifySignature(message, signature, pubkey, ch.Curve, ch.Hasher); + } + catch (ArgumentException) + { + return false; + } + } + + // This is for solving the hardfork issue in https://github.com/neo-project/neo/pull/3209 + [ContractMethod(true, Hardfork.HF_Cockatrice, CpuFee = 1 << 15, Name = "verifyWithECDsa")] + public static bool VerifyWithECDsaV0(byte[] message, byte[] pubkey, byte[] signature, NamedCurveHash curve) { + if (curve != NamedCurveHash.secp256k1SHA256 && curve != NamedCurveHash.secp256r1SHA256) + throw new ArgumentOutOfRangeException(nameof(curve)); + try { - return Crypto.VerifySignature(message, signature, pubkey, curves[curve]); + return Crypto.VerifySignature(message, signature, pubkey, s_curves[curve].Curve); } catch (ArgumentException) { diff --git a/src/Neo/SmartContract/Native/FungibleToken.cs b/src/Neo/SmartContract/Native/FungibleToken.cs index 068011fad4..4ab0f01589 100644 --- a/src/Neo/SmartContract/Native/FungibleToken.cs +++ b/src/Neo/SmartContract/Native/FungibleToken.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// FungibleToken.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -13,7 +14,6 @@ using Neo.SmartContract.Manifest; using Neo.VM.Types; using System; -using System.Collections.Generic; using System.Numerics; using Array = Neo.VM.Types.Array; @@ -56,39 +56,18 @@ public abstract class FungibleToken : NativeContract /// /// Initializes a new instance of the class. /// - protected FungibleToken() + [ContractEvent(0, name: "Transfer", + "from", ContractParameterType.Hash160, + "to", ContractParameterType.Hash160, + "amount", ContractParameterType.Integer)] + protected FungibleToken() : base() { - this.Factor = BigInteger.Pow(10, Decimals); - - Manifest.SupportedStandards = new[] { "NEP-17" }; - - var events = new List(Manifest.Abi.Events) - { - new ContractEventDescriptor - { - Name = "Transfer", - Parameters = new ContractParameterDefinition[] - { - new ContractParameterDefinition() - { - Name = "from", - Type = ContractParameterType.Hash160 - }, - new ContractParameterDefinition() - { - Name = "to", - Type = ContractParameterType.Hash160 - }, - new ContractParameterDefinition() - { - Name = "amount", - Type = ContractParameterType.Integer - } - } - } - }; + Factor = BigInteger.Pow(10, Decimals); + } - Manifest.Abi.Events = events.ToArray(); + protected override void OnManifestCompose(ContractManifest manifest) + { + manifest.SupportedStandards = new[] { "NEP-17" }; } internal async ContractTask Mint(ApplicationEngine engine, UInt160 account, BigInteger amount, bool callOnPayment) @@ -101,7 +80,7 @@ internal async ContractTask Mint(ApplicationEngine engine, UInt160 account, BigI state.Balance += amount; storage = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_TotalSupply), () => new StorageItem(BigInteger.Zero)); storage.Add(amount); - await PostTransfer(engine, null, account, amount, StackItem.Null, callOnPayment); + await PostTransferAsync(engine, null, account, amount, StackItem.Null, callOnPayment); } internal async ContractTask Burn(ApplicationEngine engine, UInt160 account, BigInteger amount) @@ -119,7 +98,7 @@ internal async ContractTask Burn(ApplicationEngine engine, UInt160 account, BigI state.Balance -= amount; storage = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_TotalSupply)); storage.Add(-amount); - await PostTransfer(engine, account, null, amount, StackItem.Null, false); + await PostTransferAsync(engine, account, null, amount, StackItem.Null, false); } /// @@ -190,7 +169,7 @@ private protected async ContractTask Transfer(ApplicationEngine engine, UI state_to.Balance += amount; } } - await PostTransfer(engine, from, to, amount, data, true); + await PostTransferAsync(engine, from, to, amount, data, true); return true; } @@ -198,7 +177,7 @@ internal virtual void OnBalanceChanging(ApplicationEngine engine, UInt160 accoun { } - private protected virtual async ContractTask PostTransfer(ApplicationEngine engine, UInt160 from, UInt160 to, BigInteger amount, StackItem data, bool callOnPayment) + private protected virtual async ContractTask PostTransferAsync(ApplicationEngine engine, UInt160 from, UInt160 to, BigInteger amount, StackItem data, bool callOnPayment) { // Send notification @@ -211,7 +190,7 @@ private protected virtual async ContractTask PostTransfer(ApplicationEngine engi // Call onNEP17Payment method - await engine.CallFromNativeContract(Hash, to, "onNEP17Payment", from?.ToArray() ?? StackItem.Null, amount, data); + await engine.CallFromNativeContractAsync(Hash, to, "onNEP17Payment", from?.ToArray() ?? StackItem.Null, amount, data); } } } diff --git a/src/Neo/SmartContract/Native/GasToken.cs b/src/Neo/SmartContract/Native/GasToken.cs index a791c891b8..b8a185b6f1 100644 --- a/src/Neo/SmartContract/Native/GasToken.cs +++ b/src/Neo/SmartContract/Native/GasToken.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// GasToken.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -25,13 +26,17 @@ internal GasToken() { } - internal override ContractTask Initialize(ApplicationEngine engine) + internal override ContractTask InitializeAsync(ApplicationEngine engine, Hardfork? hardfork) { - UInt160 account = Contract.GetBFTAddress(engine.ProtocolSettings.StandbyValidators); - return Mint(engine, account, engine.ProtocolSettings.InitialGasDistribution, false); + if (hardfork == ActiveIn) + { + UInt160 account = Contract.GetBFTAddress(engine.ProtocolSettings.StandbyValidators); + return Mint(engine, account, engine.ProtocolSettings.InitialGasDistribution, false); + } + return ContractTask.CompletedTask; } - internal override async ContractTask OnPersist(ApplicationEngine engine) + internal override async ContractTask OnPersistAsync(ApplicationEngine engine) { long totalNetworkFee = 0; foreach (Transaction tx in engine.PersistingBlock.Transactions) diff --git a/src/Neo/SmartContract/Native/HashIndexState.cs b/src/Neo/SmartContract/Native/HashIndexState.cs index 88ece82d98..229458ffdd 100644 --- a/src/Neo/SmartContract/Native/HashIndexState.cs +++ b/src/Neo/SmartContract/Native/HashIndexState.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// HashIndexState.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/SmartContract/Native/InteroperableList.cs b/src/Neo/SmartContract/Native/InteroperableList.cs index 2f62ef13b0..e118db7621 100644 --- a/src/Neo/SmartContract/Native/InteroperableList.cs +++ b/src/Neo/SmartContract/Native/InteroperableList.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// InteroperableList.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Neo.VM; using Neo.VM.Types; using System.Collections; diff --git a/src/Neo/SmartContract/Native/LedgerContract.cs b/src/Neo/SmartContract/Native/LedgerContract.cs index cd405c098b..ea757ba348 100644 --- a/src/Neo/SmartContract/Native/LedgerContract.cs +++ b/src/Neo/SmartContract/Native/LedgerContract.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// LedgerContract.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -31,11 +32,9 @@ public sealed class LedgerContract : NativeContract private const byte Prefix_Block = 5; private const byte Prefix_Transaction = 11; - internal LedgerContract() - { - } + internal LedgerContract() : base() { } - internal override ContractTask OnPersist(ApplicationEngine engine) + internal override ContractTask OnPersistAsync(ApplicationEngine engine) { TransactionState[] transactions = engine.PersistingBlock.Transactions.Select(p => new TransactionState { @@ -47,20 +46,26 @@ internal override ContractTask OnPersist(ApplicationEngine engine) engine.Snapshot.Add(CreateStorageKey(Prefix_Block).Add(engine.PersistingBlock.Hash), new StorageItem(Trim(engine.PersistingBlock).ToArray())); foreach (TransactionState tx in transactions) { - engine.Snapshot.Add(CreateStorageKey(Prefix_Transaction).Add(tx.Transaction.Hash), new StorageItem(tx)); + // It's possible that there are previously saved malicious conflict records for this transaction. + // If so, then remove it and store the relevant transaction itself. + engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_Transaction).Add(tx.Transaction.Hash), () => new StorageItem(new TransactionState())).FromReplica(new StorageItem(tx)); + + // Store transaction's conflicits. var conflictingSigners = tx.Transaction.Signers.Select(s => s.Account); foreach (var attr in tx.Transaction.GetAttributes()) { - var conflictRecord = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_Transaction).Add(attr.Hash), - () => new StorageItem(new TransactionState { ConflictingSigners = Array.Empty() })).GetInteroperable(); - conflictRecord.ConflictingSigners = conflictRecord.ConflictingSigners.Concat(conflictingSigners).Distinct().ToArray(); + engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_Transaction).Add(attr.Hash), () => new StorageItem(new TransactionState())).FromReplica(new StorageItem(new TransactionState() { BlockIndex = engine.PersistingBlock.Index })); + foreach (var signer in conflictingSigners) + { + engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_Transaction).Add(attr.Hash).Add(signer), () => new StorageItem(new TransactionState())).FromReplica(new StorageItem(new TransactionState() { BlockIndex = engine.PersistingBlock.Index })); + } } } engine.SetState(transactions); return ContractTask.CompletedTask; } - internal override ContractTask PostPersist(ApplicationEngine engine) + internal override ContractTask PostPersistAsync(ApplicationEngine engine) { HashIndexState state = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_CurrentBlock), () => new StorageItem(new HashIndexState())).GetInteroperable(); state.Hash = engine.PersistingBlock.Hash; @@ -145,11 +150,24 @@ public bool ContainsTransaction(DataCache snapshot, UInt256 hash) /// The snapshot used to read data. /// The hash of the conflicting transaction. /// The list of signer accounts of the conflicting transaction. + /// MaxTraceableBlocks protocol setting. /// if the blockchain contains the hash of the conflicting transaction; otherwise, . - public bool ContainsConflictHash(DataCache snapshot, UInt256 hash, IEnumerable signers) + public bool ContainsConflictHash(DataCache snapshot, UInt256 hash, IEnumerable signers, uint maxTraceableBlocks) { - var state = snapshot.TryGet(CreateStorageKey(Prefix_Transaction).Add(hash))?.GetInteroperable(); - return state is not null && state.Transaction is null && (signers is null || state.ConflictingSigners.Intersect(signers).Any()); + // Check the dummy stub firstly to define whether there's exist at least one conflict record. + var stub = snapshot.TryGet(CreateStorageKey(Prefix_Transaction).Add(hash))?.GetInteroperable(); + if (stub is null || stub.Transaction is not null || !IsTraceableBlock(snapshot, stub.BlockIndex, maxTraceableBlocks)) + return false; + + // At least one conflict record is found, then need to check signers intersection. + foreach (var signer in signers) + { + var state = snapshot.TryGet(CreateStorageKey(Prefix_Transaction).Add(hash).Add(signer))?.GetInteroperable(); + if (state is not null && IsTraceableBlock(snapshot, state.BlockIndex, maxTraceableBlocks)) + return true; + } + + return false; } /// diff --git a/src/Neo/SmartContract/Native/NamedCurve.cs b/src/Neo/SmartContract/Native/NamedCurve.cs deleted file mode 100644 index 8bff2bbb57..0000000000 --- a/src/Neo/SmartContract/Native/NamedCurve.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php -// for more details. -// -// Redistribution and use in source and binary forms with or without -// modifications are permitted. - -namespace Neo.SmartContract.Native -{ - /// - /// Represents the named curve used in ECDSA. - /// - /// - /// https://tools.ietf.org/html/rfc4492#section-5.1.1 - /// - public enum NamedCurve : byte - { - /// - /// The secp256k1 curve. - /// - secp256k1 = 22, - - /// - /// The secp256r1 curve, which known as prime256v1 or nistP-256. - /// - secp256r1 = 23 - } -} diff --git a/src/Neo/SmartContract/Native/NamedCurveHash.cs b/src/Neo/SmartContract/Native/NamedCurveHash.cs new file mode 100644 index 0000000000..653ed3aa88 --- /dev/null +++ b/src/Neo/SmartContract/Native/NamedCurveHash.cs @@ -0,0 +1,39 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// NamedCurveHash.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.SmartContract.Native +{ + /// + /// Represents a pair of the named curve used in ECDSA and a hash algorithm used to hash message. + /// + public enum NamedCurveHash : byte + { + /// + /// The secp256k1 curve and SHA256 hash algorithm. + /// + secp256k1SHA256 = 22, + + /// + /// The secp256r1 curve, which known as prime256v1 or nistP-256, and SHA256 hash algorithm. + /// + secp256r1SHA256 = 23, + + /// + /// The secp256k1 curve and Keccak256 hash algorithm. + /// + secp256k1Keccak256 = 122, + + /// + /// The secp256r1 curve, which known as prime256v1 or nistP-256, and Keccak256 hash algorithm. + /// + secp256r1Keccak256 = 123 + } +} diff --git a/src/Neo/SmartContract/Native/NativeContract.cs b/src/Neo/SmartContract/Native/NativeContract.cs index 63c15466fb..1cc244408d 100644 --- a/src/Neo/SmartContract/Native/NativeContract.cs +++ b/src/Neo/SmartContract/Native/NativeContract.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// NativeContract.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -13,8 +14,11 @@ using Neo.VM; using System; using System.Collections.Generic; +using System.Collections.Immutable; +using System.Collections.ObjectModel; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; namespace Neo.SmartContract.Native { @@ -23,9 +27,33 @@ namespace Neo.SmartContract.Native /// public abstract class NativeContract { - private static readonly List contractsList = new(); - private static readonly Dictionary contractsDictionary = new(); - private readonly Dictionary methods = new(); + private class NativeContractsCache + { + public class CacheEntry + { + public Dictionary Methods { get; set; } + public byte[] Script { get; set; } + } + + internal Dictionary NativeContracts { get; set; } = new(); + + public CacheEntry GetAllowedMethods(NativeContract native, ApplicationEngine engine) + { + if (NativeContracts.TryGetValue(native.Id, out var value)) return value; + + uint index = engine.PersistingBlock is null ? Ledger.CurrentIndex(engine.Snapshot) : engine.PersistingBlock.Index; + CacheEntry methods = native.GetAllowedMethods(engine.ProtocolSettings.IsHardforkEnabled, index); + NativeContracts[native.Id] = methods; + return methods; + } + } + + public delegate bool IsHardforkEnabledDelegate(Hardfork hf, uint blockHeight); + private static readonly List s_contractsList = []; + private static readonly Dictionary s_contractsDictionary = new(); + private readonly ImmutableHashSet _usedHardforks; + private readonly ReadOnlyCollection _methodDescriptors; + private readonly ReadOnlyCollection _eventsDescriptors; private static int id_counter = 0; #region Named Native Contracts @@ -80,7 +108,7 @@ public abstract class NativeContract /// /// Gets all native contracts. /// - public static IReadOnlyCollection Contracts { get; } = contractsList; + public static IReadOnlyCollection Contracts { get; } = s_contractsList; /// /// The name of the native contract. @@ -88,9 +116,9 @@ public abstract class NativeContract public string Name => GetType().Name; /// - /// The nef of the native contract. + /// Since Hardfork has to start having access to the native contract. /// - public NefFile Nef { get; } + public virtual Hardfork? ActiveIn { get; } = null; /// /// The hash of the native contract. @@ -102,28 +130,69 @@ public abstract class NativeContract /// public int Id { get; } = --id_counter; - /// - /// The manifest of the native contract. - /// - public ContractManifest Manifest { get; } - /// /// Initializes a new instance of the class. /// protected NativeContract() { - List descriptors = new(); + Hash = Helper.GetContractHash(UInt160.Zero, 0, Name); + + // Reflection to get the methods + + List listMethods = []; foreach (MemberInfo member in GetType().GetMembers(BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)) { ContractMethodAttribute attribute = member.GetCustomAttribute(); if (attribute is null) continue; - descriptors.Add(new ContractMethodMetadata(member, attribute)); + listMethods.Add(new ContractMethodMetadata(member, attribute)); } - descriptors = descriptors.OrderBy(p => p.Name, StringComparer.Ordinal).ThenBy(p => p.Parameters.Length).ToList(); + _methodDescriptors = listMethods.OrderBy(p => p.Name, StringComparer.Ordinal).ThenBy(p => p.Parameters.Length).ToList().AsReadOnly(); + + // Reflection to get the events + _eventsDescriptors = + GetType().GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, null, Array.Empty(), null)?. + GetCustomAttributes(). + // Take into account not only the contract constructor, but also the base type constructor for proper FungibleToken events handling. + Concat(GetType().BaseType?.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, null, Array.Empty(), null)?. + GetCustomAttributes()). + OrderBy(p => p.Order).ToList().AsReadOnly(); + + // Calculate the initializations forks + _usedHardforks = + _methodDescriptors.Select(u => u.ActiveIn) + .Concat(_methodDescriptors.Select(u => u.DeprecatedIn)) + .Concat(_eventsDescriptors.Select(u => u.ActiveIn)) + .Concat([ActiveIn]) + .Where(u => u is not null) + .OrderBy(u => (byte)u) + .Cast().ToImmutableHashSet(); + s_contractsList.Add(this); + s_contractsDictionary.Add(Hash, this); + } + + /// + /// The allowed methods and his offsets. + /// + /// Hardfork checker + /// Block height. Used to check the hardforks and active methods. + /// The . + private NativeContractsCache.CacheEntry GetAllowedMethods(IsHardforkEnabledDelegate hfChecker, uint blockHeight) + { + Dictionary methods = new(); + + // Reflection to get the ContractMethods byte[] script; using (ScriptBuilder sb = new()) { - foreach (ContractMethodMetadata method in descriptors) + foreach (ContractMethodMetadata method in _methodDescriptors.Where(u + => + // no hardfork is involved + u.ActiveIn is null && u.DeprecatedIn is null || + // deprecated method hardfork is involved + u.DeprecatedIn is not null && hfChecker(u.DeprecatedIn.Value, blockHeight) == false || + // active method hardfork is involved + u.ActiveIn is not null && hfChecker(u.ActiveIn.Value, blockHeight)) + ) { method.Descriptor.Offset = sb.Length; sb.EmitPush(0); //version @@ -133,31 +202,135 @@ protected NativeContract() } script = sb.ToArray(); } - this.Nef = new NefFile + + return new NativeContractsCache.CacheEntry { Methods = methods, Script = script }; + } + + /// + /// The of the native contract. + /// + /// The where the HardForks are configured. + /// Block index + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ContractState GetContractState(ProtocolSettings settings, uint blockHeight) => GetContractState(settings.IsHardforkEnabled, blockHeight); + + /// + /// The of the native contract. + /// + /// Hardfork checker + /// Block height. Used to check hardforks and active methods. + /// The . + public ContractState GetContractState(IsHardforkEnabledDelegate hfChecker, uint blockHeight) + { + // Get allowed methods and nef script + var allowedMethods = GetAllowedMethods(hfChecker, blockHeight); + + // Compose nef file + var nef = new NefFile() { Compiler = "neo-core-v3.0", Source = string.Empty, - Tokens = Array.Empty(), - Script = script + Tokens = [], + Script = allowedMethods.Script }; - this.Nef.CheckSum = NefFile.ComputeChecksum(Nef); - this.Hash = Helper.GetContractHash(UInt160.Zero, 0, Name); - this.Manifest = new ContractManifest + nef.CheckSum = NefFile.ComputeChecksum(nef); + + // Compose manifest + var manifest = new ContractManifest() { Name = Name, - Groups = Array.Empty(), - SupportedStandards = Array.Empty(), - Abi = new ContractAbi() + Groups = [], + SupportedStandards = [], + Abi = new ContractAbi { - Events = Array.Empty(), - Methods = descriptors.Select(p => p.Descriptor).ToArray() + Events = _eventsDescriptors + .Where(u => u.ActiveIn is null || hfChecker(u.ActiveIn.Value, blockHeight)) + .Select(p => p.Descriptor).ToArray(), + Methods = allowedMethods.Methods.Values + .Select(p => p.Descriptor).ToArray() }, - Permissions = new[] { ContractPermission.DefaultPermission }, + Permissions = [ContractPermission.DefaultPermission], Trusts = WildcardContainer.Create(), Extra = null }; - contractsList.Add(this); - contractsDictionary.Add(Hash, this); + + OnManifestCompose(manifest); + + // Return ContractState + return new ContractState + { + Id = Id, + Nef = nef, + Hash = Hash, + Manifest = manifest + }; + } + + protected virtual void OnManifestCompose(ContractManifest manifest) { } + + /// + /// It is the initialize block + /// + /// The where the HardForks are configured. + /// Block index + /// Active hardforks + /// True if the native contract must be initialized + internal bool IsInitializeBlock(ProtocolSettings settings, uint index, out Hardfork[] hardforks) + { + var hfs = new List(); + + // If is in the hardfork height, add them to return array + foreach (var hf in _usedHardforks) + { + if (!settings.Hardforks.TryGetValue(hf, out var activeIn)) + { + // If is not set in the configuration is treated as enabled from the genesis + activeIn = 0; + } + + if (activeIn == index) + { + hfs.Add(hf); + } + } + + // Return all initialize hardforks + if (hfs.Count > 0) + { + hardforks = hfs.ToArray(); + return true; + } + + // If is not configured, the Genesis is an initialization block. + if (index == 0 && ActiveIn is null) + { + hardforks = hfs.ToArray(); + return true; + } + + // Initialized not required + hardforks = null; + return false; + } + + /// + /// Is the native contract active + /// + /// The where the HardForks are configured. + /// Block height + /// True if the native contract is active + internal bool IsActive(ProtocolSettings settings, uint blockHeight) + { + if (ActiveIn is null) return true; + + if (!settings.Hardforks.TryGetValue(ActiveIn.Value, out var activeIn)) + { + // If is not set in the configuration is treated as enabled from the genesis + activeIn = 0; + } + + return activeIn <= blockHeight; } /// @@ -183,7 +356,7 @@ private protected KeyBuilder CreateStorageKey(byte prefix) /// The native contract with the specified hash. public static NativeContract GetContract(UInt160 hash) { - contractsDictionary.TryGetValue(hash, out var contract); + s_contractsDictionary.TryGetValue(hash, out var contract); return contract; } @@ -193,12 +366,21 @@ internal async void Invoke(ApplicationEngine engine, byte version) { if (version != 0) throw new InvalidOperationException($"The native contract of version {version} is not active."); + // Get native contracts invocation cache + NativeContractsCache nativeContracts = engine.GetState(() => new NativeContractsCache()); + NativeContractsCache.CacheEntry currentAllowedMethods = nativeContracts.GetAllowedMethods(this, engine); + // Check if the method is allowed ExecutionContext context = engine.CurrentContext; - ContractMethodMetadata method = methods[context.InstructionPointer]; + ContractMethodMetadata method = currentAllowedMethods.Methods[context.InstructionPointer]; + if (method.ActiveIn is not null && !engine.IsHardforkEnabled(method.ActiveIn.Value)) + throw new InvalidOperationException($"Cannot call this method before hardfork {method.ActiveIn}."); + if (method.DeprecatedIn is not null && engine.IsHardforkEnabled(method.DeprecatedIn.Value)) + throw new InvalidOperationException($"Cannot call this method after hardfork {method.DeprecatedIn}."); ExecutionContextState state = context.GetState(); if (!state.CallFlags.HasFlag(method.RequiredCallFlags)) throw new InvalidOperationException($"Cannot call this method with the flag {state.CallFlags}."); - engine.AddGas(method.CpuFee * engine.ExecFeeFactor + method.StorageFee * engine.StoragePrice); + // In the unit of datoshi, 1 datoshi = 1e-8 GAS + engine.AddFee(method.CpuFee * engine.ExecFeeFactor + method.StorageFee * engine.StoragePrice); List parameters = new(); if (method.NeedApplicationEngine) parameters.Add(engine); if (method.NeedSnapshot) parameters.Add(engine.Snapshot); @@ -232,20 +414,20 @@ internal async void Invoke(ApplicationEngine engine, byte version) /// if the contract is native; otherwise, . public static bool IsNative(UInt160 hash) { - return contractsDictionary.ContainsKey(hash); + return s_contractsDictionary.ContainsKey(hash); } - internal virtual ContractTask Initialize(ApplicationEngine engine) + internal virtual ContractTask InitializeAsync(ApplicationEngine engine, Hardfork? hardFork) { return ContractTask.CompletedTask; } - internal virtual ContractTask OnPersist(ApplicationEngine engine) + internal virtual ContractTask OnPersistAsync(ApplicationEngine engine) { return ContractTask.CompletedTask; } - internal virtual ContractTask PostPersist(ApplicationEngine engine) + internal virtual ContractTask PostPersistAsync(ApplicationEngine engine) { return ContractTask.CompletedTask; } diff --git a/src/Neo/SmartContract/Native/NeoToken.cs b/src/Neo/SmartContract/Native/NeoToken.cs index 74fe70db37..99ecd41e29 100644 --- a/src/Neo/SmartContract/Native/NeoToken.cs +++ b/src/Neo/SmartContract/Native/NeoToken.cs @@ -1,27 +1,27 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// NeoToken.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. #pragma warning disable IDE0051 -using System; -using System.Buffers.Binary; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; using Neo.Cryptography.ECC; using Neo.IO; using Neo.Persistence; using Neo.SmartContract.Iterators; -using Neo.SmartContract.Manifest; using Neo.VM; using Neo.VM.Types; +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; namespace Neo.SmartContract.Native { @@ -54,64 +54,21 @@ public sealed class NeoToken : FungibleToken private const byte CommitteeRewardRatio = 10; private const byte VoterRewardRatio = 80; - internal NeoToken() + [ContractEvent(1, name: "CandidateStateChanged", + "pubkey", ContractParameterType.PublicKey, + "registered", ContractParameterType.Boolean, + "votes", ContractParameterType.Integer)] + [ContractEvent(2, name: "Vote", + "account", ContractParameterType.Hash160, + "from", ContractParameterType.PublicKey, + "to", ContractParameterType.PublicKey, + "amount", ContractParameterType.Integer)] + [ContractEvent(Hardfork.HF_Cockatrice, 3, name: "CommitteeChanged", + "old", ContractParameterType.Array, + "new", ContractParameterType.Array)] + internal NeoToken() : base() { - this.TotalAmount = 100000000 * Factor; - - var events = new List(Manifest.Abi.Events) - { - new ContractEventDescriptor - { - Name = "CandidateStateChanged", - Parameters = new ContractParameterDefinition[] - { - new ContractParameterDefinition() - { - Name = "pubkey", - Type = ContractParameterType.PublicKey - }, - new ContractParameterDefinition() - { - Name = "registered", - Type = ContractParameterType.Boolean - }, - new ContractParameterDefinition() - { - Name = "votes", - Type = ContractParameterType.Integer - } - } - }, - new ContractEventDescriptor - { - Name = "Vote", - Parameters = new ContractParameterDefinition[] - { - new ContractParameterDefinition() - { - Name = "account", - Type = ContractParameterType.Hash160 - }, - new ContractParameterDefinition() - { - Name = "from", - Type = ContractParameterType.PublicKey - }, - new ContractParameterDefinition() - { - Name = "to", - Type = ContractParameterType.PublicKey - }, - new ContractParameterDefinition() - { - Name = "amount", - Type = ContractParameterType.Integer - } - } - } - }; - - Manifest.Abi.Events = events.ToArray(); + TotalAmount = 100000000 * Factor; } public override BigInteger TotalSupply(DataCache snapshot) @@ -136,9 +93,9 @@ internal override void OnBalanceChanging(ApplicationEngine engine, UInt160 accou CheckCandidate(engine.Snapshot, state.VoteTo, candidate); } - private protected override async ContractTask PostTransfer(ApplicationEngine engine, UInt160 from, UInt160 to, BigInteger amount, StackItem data, bool callOnPayment) + private protected override async ContractTask PostTransferAsync(ApplicationEngine engine, UInt160 from, UInt160 to, BigInteger amount, StackItem data, bool callOnPayment) { - await base.PostTransfer(engine, from, to, amount, data, callOnPayment); + await base.PostTransferAsync(engine, from, to, amount, data, callOnPayment); var list = engine.CurrentContext.GetState>(); foreach (var distribution in list) await GAS.Mint(engine, distribution.Account, distribution.Amount, callOnPayment); @@ -149,7 +106,8 @@ private GasDistribution DistributeGas(ApplicationEngine engine, UInt160 account, // PersistingBlock is null when running under the debugger if (engine.PersistingBlock is null) return null; - BigInteger gas = CalculateBonus(engine.Snapshot, state, engine.PersistingBlock.Index); + // In the unit of datoshi, 1 datoshi = 1e-8 GAS + BigInteger datoshi = CalculateBonus(engine.Snapshot, state, engine.PersistingBlock.Index); state.BalanceHeight = engine.PersistingBlock.Index; if (state.VoteTo is not null) { @@ -157,11 +115,11 @@ private GasDistribution DistributeGas(ApplicationEngine engine, UInt160 account, var latestGasPerVote = engine.Snapshot.TryGet(keyLastest) ?? BigInteger.Zero; state.LastGasPerVote = latestGasPerVote; } - if (gas == 0) return null; + if (datoshi == 0) return null; return new GasDistribution { Account = account, - Amount = gas + Amount = datoshi }; } @@ -173,6 +131,7 @@ private BigInteger CalculateBonus(DataCache snapshot, NeoAccountState state, uin var expectEnd = Ledger.CurrentIndex(snapshot) + 1; if (expectEnd != end) throw new ArgumentOutOfRangeException(nameof(end)); if (state.BalanceHeight >= end) return BigInteger.Zero; + // In the unit of datoshi, 1 datoshi = 1e-8 GAS BigInteger neoHolderReward = CalculateNeoHolderReward(snapshot, state.Balance, state.BalanceHeight, end); if (state.VoteTo is null) return neoHolderReward; @@ -185,6 +144,7 @@ private BigInteger CalculateBonus(DataCache snapshot, NeoAccountState state, uin private BigInteger CalculateNeoHolderReward(DataCache snapshot, BigInteger value, uint start, uint end) { + // In the unit of datoshi, 1 GAS = 10^8 datoshi BigInteger sum = 0; foreach (var (index, gasPerBlock) in GetSortedGasRecords(snapshot, end - 1)) { @@ -219,30 +179,53 @@ private void CheckCandidate(DataCache snapshot, ECPoint pubkey, CandidateState c /// if the votes should be recounted; otherwise, . public static bool ShouldRefreshCommittee(uint height, int committeeMembersCount) => height % committeeMembersCount == 0; - internal override ContractTask Initialize(ApplicationEngine engine) + internal override ContractTask InitializeAsync(ApplicationEngine engine, Hardfork? hardfork) { - var cachedCommittee = new CachedCommittee(engine.ProtocolSettings.StandbyCommittee.Select(p => (p, BigInteger.Zero))); - engine.Snapshot.Add(CreateStorageKey(Prefix_Committee), new StorageItem(cachedCommittee)); - engine.Snapshot.Add(CreateStorageKey(Prefix_VotersCount), new StorageItem(System.Array.Empty())); - engine.Snapshot.Add(CreateStorageKey(Prefix_GasPerBlock).AddBigEndian(0u), new StorageItem(5 * GAS.Factor)); - engine.Snapshot.Add(CreateStorageKey(Prefix_RegisterPrice), new StorageItem(1000 * GAS.Factor)); - return Mint(engine, Contract.GetBFTAddress(engine.ProtocolSettings.StandbyValidators), TotalAmount, false); + if (hardfork == ActiveIn) + { + var cachedCommittee = new CachedCommittee(engine.ProtocolSettings.StandbyCommittee.Select(p => (p, BigInteger.Zero))); + engine.Snapshot.Add(CreateStorageKey(Prefix_Committee), new StorageItem(cachedCommittee)); + engine.Snapshot.Add(CreateStorageKey(Prefix_VotersCount), new StorageItem(System.Array.Empty())); + engine.Snapshot.Add(CreateStorageKey(Prefix_GasPerBlock).AddBigEndian(0u), new StorageItem(5 * GAS.Factor)); + engine.Snapshot.Add(CreateStorageKey(Prefix_RegisterPrice), new StorageItem(1000 * GAS.Factor)); + return Mint(engine, Contract.GetBFTAddress(engine.ProtocolSettings.StandbyValidators), TotalAmount, false); + } + return ContractTask.CompletedTask; } - internal override ContractTask OnPersist(ApplicationEngine engine) + internal override ContractTask OnPersistAsync(ApplicationEngine engine) { // Set next committee if (ShouldRefreshCommittee(engine.PersistingBlock.Index, engine.ProtocolSettings.CommitteeMembersCount)) { - StorageItem storageItem = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_Committee)); + var storageItem = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_Committee)); var cachedCommittee = storageItem.GetInteroperable(); + + var prevCommittee = cachedCommittee.Select(u => u.PublicKey).ToArray(); + cachedCommittee.Clear(); cachedCommittee.AddRange(ComputeCommitteeMembers(engine.Snapshot, engine.ProtocolSettings)); + + // Hardfork check for https://github.com/neo-project/neo/pull/3158 + // New notification will case 3.7.0 and 3.6.0 have different behavior + var index = engine.PersistingBlock?.Index ?? Ledger.CurrentIndex(engine.Snapshot); + if (engine.ProtocolSettings.IsHardforkEnabled(Hardfork.HF_Cockatrice, index)) + { + var newCommittee = cachedCommittee.Select(u => u.PublicKey).ToArray(); + + if (!newCommittee.SequenceEqual(prevCommittee)) + { + engine.SendNotification(Hash, "CommitteeChanged", new VM.Types.Array(engine.ReferenceCounter) { + new VM.Types.Array(engine.ReferenceCounter, prevCommittee.Select(u => (ByteString)u.ToArray())) , + new VM.Types.Array(engine.ReferenceCounter, newCommittee.Select(u => (ByteString)u.ToArray())) + }); + } + } } return ContractTask.CompletedTask; } - internal override async ContractTask PostPersist(ApplicationEngine engine) + internal override async ContractTask PostPersistAsync(ApplicationEngine engine) { // Distribute GAS for committee @@ -315,6 +298,7 @@ private void SetRegisterPrice(ApplicationEngine engine, long registerPrice) [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public long GetRegisterPrice(DataCache snapshot) { + // In the unit of datoshi, 1 datoshi = 1e-8 GAS return (long)(BigInteger)snapshot[CreateStorageKey(Prefix_RegisterPrice)]; } @@ -347,7 +331,8 @@ private bool RegisterCandidate(ApplicationEngine engine, ECPoint pubkey) { if (!engine.CheckWitnessInternal(Contract.CreateSignatureRedeemScript(pubkey).ToScriptHash())) return false; - engine.AddGas(GetRegisterPrice(engine.Snapshot)); + // In the unit of datoshi, 1 datoshi = 1e-8 GAS + engine.AddFee(GetRegisterPrice(engine.Snapshot)); StorageKey key = CreateStorageKey(Prefix_Candidate).Add(pubkey); StorageItem item = engine.Snapshot.GetAndChange(key, () => new StorageItem(new CandidateState())); CandidateState state = item.GetInteroperable(); @@ -419,6 +404,10 @@ private async ContractTask Vote(ApplicationEngine engine, UInt160 account, { validator_new.Votes += state_account.Balance; } + else + { + state_account.LastGasPerVote = 0; + } engine.SendNotification(Hash, "Vote", new VM.Types.Array(engine.ReferenceCounter) { account.ToArray(), from?.ToArray() ?? StackItem.Null, voteTo?.ToArray() ?? StackItem.Null, state_account.Balance }); if (gasDistribution is not null) @@ -506,6 +495,7 @@ public NeoAccountState GetAccountState(DataCache snapshot, UInt160 account) /// /// The snapshot used to read data. /// The address of the committee. + [ContractMethod(Hardfork.HF_Cockatrice, CpuFee = 1 << 16, RequiredCallFlags = CallFlags.ReadStates)] public UInt160 GetCommitteeAddress(DataCache snapshot) { ECPoint[] committees = GetCommittee(snapshot); diff --git a/src/Neo/SmartContract/Native/OracleContract.cs b/src/Neo/SmartContract/Native/OracleContract.cs index df35bd98e8..13a7264660 100644 --- a/src/Neo/SmartContract/Native/OracleContract.cs +++ b/src/Neo/SmartContract/Native/OracleContract.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// OracleContract.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -14,7 +15,6 @@ using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.Persistence; -using Neo.SmartContract.Manifest; using Neo.VM; using Neo.VM.Types; using System; @@ -40,58 +40,15 @@ public sealed class OracleContract : NativeContract private const byte Prefix_Request = 7; private const byte Prefix_IdList = 6; - internal OracleContract() - { - var events = new List(Manifest.Abi.Events) - { - new ContractEventDescriptor - { - Name = "OracleRequest", - Parameters = new ContractParameterDefinition[] - { - new ContractParameterDefinition() - { - Name = "Id", - Type = ContractParameterType.Integer - }, - new ContractParameterDefinition() - { - Name = "RequestContract", - Type = ContractParameterType.Hash160 - }, - new ContractParameterDefinition() - { - Name = "Url", - Type = ContractParameterType.String - }, - new ContractParameterDefinition() - { - Name = "Filter", - Type = ContractParameterType.String - } - } - }, - new ContractEventDescriptor - { - Name = "OracleResponse", - Parameters = new ContractParameterDefinition[] - { - new ContractParameterDefinition() - { - Name = "Id", - Type = ContractParameterType.Integer - }, - new ContractParameterDefinition() - { - Name = "OriginalTx", - Type = ContractParameterType.Hash256 - } - } - } - }; - - Manifest.Abi.Events = events.ToArray(); - } + [ContractEvent(0, name: "OracleRequest", + "Id", ContractParameterType.Integer, + "RequestContract", ContractParameterType.Hash160, + "Url", ContractParameterType.String, + "Filter", ContractParameterType.String)] + [ContractEvent(1, name: "OracleResponse", + "Id", ContractParameterType.Integer, + "OriginalTx", ContractParameterType.Hash256)] + internal OracleContract() : base() { } [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] private void SetPrice(ApplicationEngine engine, long price) @@ -106,7 +63,7 @@ private void SetPrice(ApplicationEngine engine, long price) /// Gets the price for an Oracle request. /// /// The snapshot used to read data. - /// The price for an Oracle request. + /// The price for an Oracle request, in the unit of datoshi, 1 datoshi = 1e-8 GAS. [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] public long GetPrice(DataCache snapshot) { @@ -125,7 +82,7 @@ private ContractTask Finish(ApplicationEngine engine) if (request == null) throw new ArgumentException("Oracle request was not found"); engine.SendNotification(Hash, "OracleResponse", new VM.Types.Array(engine.ReferenceCounter) { response.Id, request.OriginalTxid.ToArray() }); StackItem userData = BinarySerializer.Deserialize(request.UserData, engine.Limits, engine.ReferenceCounter); - return engine.CallFromNativeContract(Hash, request.CallbackContract, request.CallbackMethod, request.Url, userData, (int)response.Code, response.Result); + return engine.CallFromNativeContractAsync(Hash, request.CallbackContract, request.CallbackMethod, request.Url, userData, (int)response.Code, response.Result); } private UInt256 GetOriginalTxid(ApplicationEngine engine) @@ -177,14 +134,17 @@ private static byte[] GetUrlHash(string url) return Crypto.Hash160(Utility.StrictUTF8.GetBytes(url)); } - internal override ContractTask Initialize(ApplicationEngine engine) + internal override ContractTask InitializeAsync(ApplicationEngine engine, Hardfork? hardfork) { - engine.Snapshot.Add(CreateStorageKey(Prefix_RequestId), new StorageItem(BigInteger.Zero)); - engine.Snapshot.Add(CreateStorageKey(Prefix_Price), new StorageItem(0_50000000)); + if (hardfork == ActiveIn) + { + engine.Snapshot.Add(CreateStorageKey(Prefix_RequestId), new StorageItem(BigInteger.Zero)); + engine.Snapshot.Add(CreateStorageKey(Prefix_Price), new StorageItem(0_50000000)); + } return ContractTask.CompletedTask; } - internal override async ContractTask PostPersist(ApplicationEngine engine) + internal override async ContractTask PostPersistAsync(ApplicationEngine engine) { (UInt160 Account, BigInteger GAS)[] nodes = null; foreach (Transaction tx in engine.PersistingBlock.Transactions) @@ -224,7 +184,7 @@ internal override async ContractTask PostPersist(ApplicationEngine engine) } [ContractMethod(RequiredCallFlags = CallFlags.States | CallFlags.AllowNotify)] - private async ContractTask Request(ApplicationEngine engine, string url, string filter, string callback, StackItem userData, long gasForResponse) + private async ContractTask Request(ApplicationEngine engine, string url, string filter, string callback, StackItem userData, long gasForResponse /* In the unit of datoshi, 1 datoshi = 1e-8 GAS */) { //Check the arguments if (Utility.StrictUTF8.GetByteCount(url) > MaxUrlLength @@ -233,10 +193,10 @@ private async ContractTask Request(ApplicationEngine engine, string url, string || gasForResponse < 0_10000000) throw new ArgumentException(); - engine.AddGas(GetPrice(engine.Snapshot)); + engine.AddFee(GetPrice(engine.Snapshot)); //Mint gas for the response - engine.AddGas(gasForResponse); + engine.AddFee(gasForResponse); await GAS.Mint(engine, Hash, gasForResponse, false); //Increase the request id @@ -255,7 +215,7 @@ private async ContractTask Request(ApplicationEngine engine, string url, string Filter = filter, CallbackContract = engine.CallingScriptHash, CallbackMethod = callback, - UserData = BinarySerializer.Serialize(userData, MaxUserDataLength) + UserData = BinarySerializer.Serialize(userData, MaxUserDataLength, engine.Limits.MaxStackSize) })); //Add the id to the IdList diff --git a/src/Neo/SmartContract/Native/OracleRequest.cs b/src/Neo/SmartContract/Native/OracleRequest.cs index c02af2da38..d18968ef00 100644 --- a/src/Neo/SmartContract/Native/OracleRequest.cs +++ b/src/Neo/SmartContract/Native/OracleRequest.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// OracleRequest.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/SmartContract/Native/PolicyContract.cs b/src/Neo/SmartContract/Native/PolicyContract.cs index 536bc65691..2da6255094 100644 --- a/src/Neo/SmartContract/Native/PolicyContract.cs +++ b/src/Neo/SmartContract/Native/PolicyContract.cs @@ -1,15 +1,17 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// PolicyContract.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. #pragma warning disable IDE0051 +using Neo.Network.P2P.Payloads; using Neo.Persistence; using System; using System.Numerics; @@ -33,14 +35,25 @@ public sealed class PolicyContract : NativeContract /// /// The default network fee per byte of transactions. + /// In the unit of datoshi, 1 datoshi = 1e-8 GAS /// public const uint DefaultFeePerByte = 1000; + /// + /// The default fee for attribute. + /// + public const uint DefaultAttributeFee = 0; + /// /// The maximum execution fee factor that the committee can set. /// public const uint MaxExecFeeFactor = 100; + /// + /// The maximum fee for attribute that the committee can set. + /// + public const uint MaxAttributeFee = 10_0000_0000; + /// /// The maximum storage price that the committee can set. /// @@ -50,16 +63,18 @@ public sealed class PolicyContract : NativeContract private const byte Prefix_FeePerByte = 10; private const byte Prefix_ExecFeeFactor = 18; private const byte Prefix_StoragePrice = 19; + private const byte Prefix_AttributeFee = 20; - internal PolicyContract() - { - } + internal PolicyContract() : base() { } - internal override ContractTask Initialize(ApplicationEngine engine) + internal override ContractTask InitializeAsync(ApplicationEngine engine, Hardfork? hardfork) { - engine.Snapshot.Add(CreateStorageKey(Prefix_FeePerByte), new StorageItem(DefaultFeePerByte)); - engine.Snapshot.Add(CreateStorageKey(Prefix_ExecFeeFactor), new StorageItem(DefaultExecFeeFactor)); - engine.Snapshot.Add(CreateStorageKey(Prefix_StoragePrice), new StorageItem(DefaultStoragePrice)); + if (hardfork == ActiveIn) + { + engine.Snapshot.Add(CreateStorageKey(Prefix_FeePerByte), new StorageItem(DefaultFeePerByte)); + engine.Snapshot.Add(CreateStorageKey(Prefix_ExecFeeFactor), new StorageItem(DefaultExecFeeFactor)); + engine.Snapshot.Add(CreateStorageKey(Prefix_StoragePrice), new StorageItem(DefaultStoragePrice)); + } return ContractTask.CompletedTask; } @@ -96,6 +111,22 @@ public uint GetStoragePrice(DataCache snapshot) return (uint)(BigInteger)snapshot[CreateStorageKey(Prefix_StoragePrice)]; } + /// + /// Gets the fee for attribute. + /// + /// The snapshot used to read data. + /// Attribute type + /// The fee for attribute. + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] + public uint GetAttributeFee(DataCache snapshot, byte attributeType) + { + if (!Enum.IsDefined(typeof(TransactionAttributeType), attributeType)) throw new InvalidOperationException(); + StorageItem entry = snapshot.TryGet(CreateStorageKey(Prefix_AttributeFee).Add(attributeType)); + if (entry == null) return DefaultAttributeFee; + + return (uint)(BigInteger)entry; + } + /// /// Determines whether the specified account is blocked. /// @@ -108,6 +139,16 @@ public bool IsBlocked(DataCache snapshot, UInt160 account) return snapshot.Contains(CreateStorageKey(Prefix_BlockedAccount).Add(account)); } + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] + private void SetAttributeFee(ApplicationEngine engine, byte attributeType, uint value) + { + if (!Enum.IsDefined(typeof(TransactionAttributeType), attributeType)) throw new InvalidOperationException(); + if (value > MaxAttributeFee) throw new ArgumentOutOfRangeException(nameof(value)); + if (!CheckCommittee(engine)) throw new InvalidOperationException(); + + engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_AttributeFee).Add(attributeType), () => new StorageItem(DefaultAttributeFee)).Set(value); + } + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] private void SetFeePerByte(ApplicationEngine engine, long value) { diff --git a/src/Neo/SmartContract/Native/Role.cs b/src/Neo/SmartContract/Native/Role.cs index 270861ef8f..710cc6f390 100644 --- a/src/Neo/SmartContract/Native/Role.cs +++ b/src/Neo/SmartContract/Native/Role.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// Role.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -28,6 +29,11 @@ public enum Role : byte /// /// NeoFS Alphabet nodes. /// - NeoFSAlphabetNode = 16 + NeoFSAlphabetNode = 16, + + /// + /// P2P Notary nodes used to process P2P notary requests. + /// + P2PNotary = 32 } } diff --git a/src/Neo/SmartContract/Native/RoleManagement.cs b/src/Neo/SmartContract/Native/RoleManagement.cs index 5a32f10f32..6e989b78cf 100644 --- a/src/Neo/SmartContract/Native/RoleManagement.cs +++ b/src/Neo/SmartContract/Native/RoleManagement.cs @@ -1,21 +1,20 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// RoleManagement.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. using Neo.Cryptography.ECC; using Neo.IO; using Neo.Persistence; -using Neo.SmartContract.Manifest; using Neo.VM; using Neo.VM.Types; using System; -using System.Collections.Generic; using System.Linq; namespace Neo.SmartContract.Native @@ -25,31 +24,10 @@ namespace Neo.SmartContract.Native /// public sealed class RoleManagement : NativeContract { - internal RoleManagement() - { - var events = new List(Manifest.Abi.Events) - { - new ContractEventDescriptor - { - Name = "Designation", - Parameters = new ContractParameterDefinition[] - { - new ContractParameterDefinition() - { - Name = "Role", - Type = ContractParameterType.Integer - }, - new ContractParameterDefinition() - { - Name = "BlockIndex", - Type = ContractParameterType.Integer - } - } - } - }; - - Manifest.Abi.Events = events.ToArray(); - } + [ContractEvent(0, name: "Designation", + "Role", ContractParameterType.Integer, + "BlockIndex", ContractParameterType.Integer)] + internal RoleManagement() : base() { } /// /// Gets the list of nodes for the specified role. diff --git a/src/Neo/SmartContract/Native/StdLib.cs b/src/Neo/SmartContract/Native/StdLib.cs index 709a85724f..f8fa9efcc2 100644 --- a/src/Neo/SmartContract/Native/StdLib.cs +++ b/src/Neo/SmartContract/Native/StdLib.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// StdLib.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -26,12 +27,12 @@ public sealed class StdLib : NativeContract { private const int MaxInputLength = 1024; - internal StdLib() { } + internal StdLib() : base() { } [ContractMethod(CpuFee = 1 << 12)] private static byte[] Serialize(ApplicationEngine engine, StackItem item) { - return BinarySerializer.Serialize(item, engine.Limits.MaxItemSize); + return BinarySerializer.Serialize(item, engine.Limits); } [ContractMethod(CpuFee = 1 << 14)] @@ -222,5 +223,22 @@ private static string[] StringSplit([MaxLength(MaxInputLength)] string str, stri StringSplitOptions options = removeEmptyEntries ? StringSplitOptions.RemoveEmptyEntries : StringSplitOptions.None; return str.Split(separator, options); } + + [ContractMethod(CpuFee = 1 << 8)] + private static int StrLen([MaxLength(MaxInputLength)] string str) + { + // return the length of the string in elements + // it should return 1 for both "🦆" and "ã" + + TextElementEnumerator enumerator = StringInfo.GetTextElementEnumerator(str); + int count = 0; + + while (enumerator.MoveNext()) + { + count++; + } + + return count; + } } } diff --git a/src/Neo/SmartContract/Native/TransactionState.cs b/src/Neo/SmartContract/Native/TransactionState.cs index d3c7f644be..b17296b42d 100644 --- a/src/Neo/SmartContract/Native/TransactionState.cs +++ b/src/Neo/SmartContract/Native/TransactionState.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// TransactionState.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -32,8 +33,6 @@ public class TransactionState : IInteroperable /// public Transaction Transaction; - public UInt160[] ConflictingSigners; - /// /// The execution state /// @@ -47,7 +46,6 @@ IInteroperable IInteroperable.Clone() { BlockIndex = BlockIndex, Transaction = Transaction, - ConflictingSigners = ConflictingSigners, State = State, _rawTransaction = _rawTransaction }; @@ -58,7 +56,6 @@ void IInteroperable.FromReplica(IInteroperable replica) TransactionState from = (TransactionState)replica; BlockIndex = from.BlockIndex; Transaction = from.Transaction; - ConflictingSigners = from.ConflictingSigners; State = from.State; if (_rawTransaction.IsEmpty) _rawTransaction = from._rawTransaction; @@ -67,12 +64,12 @@ void IInteroperable.FromReplica(IInteroperable replica) void IInteroperable.FromStackItem(StackItem stackItem) { Struct @struct = (Struct)stackItem; - if (@struct.Count == 1) - { - ConflictingSigners = ((VM.Types.Array)@struct[0]).Select(u => new UInt160(u.GetSpan())).ToArray(); - return; - } BlockIndex = (uint)@struct[0].GetInteger(); + + // Conflict record. + if (@struct.Count == 1) return; + + // Fully-qualified transaction. _rawTransaction = ((ByteString)@struct[1]).Memory; Transaction = _rawTransaction.AsSerializable(); State = (VMState)(byte)@struct[2].GetInteger(); @@ -80,7 +77,8 @@ void IInteroperable.FromStackItem(StackItem stackItem) StackItem IInteroperable.ToStackItem(ReferenceCounter referenceCounter) { - if (Transaction is null) return new Struct(referenceCounter) { new VM.Types.Array(referenceCounter, ConflictingSigners.Select(u => new ByteString(u.ToArray())).ToArray()) }; + if (Transaction is null) + return new Struct(referenceCounter) { BlockIndex }; if (_rawTransaction.IsEmpty) _rawTransaction = Transaction.ToArray(); return new Struct(referenceCounter) { BlockIndex, _rawTransaction, (byte)State }; diff --git a/src/Neo/SmartContract/Native/TrimmedBlock.cs b/src/Neo/SmartContract/Native/TrimmedBlock.cs index 0fd8aea5f8..4cc4c39c0f 100644 --- a/src/Neo/SmartContract/Native/TrimmedBlock.cs +++ b/src/Neo/SmartContract/Native/TrimmedBlock.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// TrimmedBlock.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/SmartContract/NefFile.cs b/src/Neo/SmartContract/NefFile.cs index 346b74bf4c..3e343d3be2 100644 --- a/src/Neo/SmartContract/NefFile.cs +++ b/src/Neo/SmartContract/NefFile.cs @@ -1,16 +1,18 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// NefFile.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. using Neo.Cryptography; using Neo.IO; using Neo.Json; +using Neo.VM; using System; using System.Buffers.Binary; using System.IO; @@ -71,11 +73,6 @@ public class NefFile : ISerializable /// public uint CheckSum { get; set; } - /// - /// The maximum length of the script. - /// - public const int MaxScriptLength = 512 * 1024; - private const int HeaderSize = sizeof(uint) + // Magic 64; // Compiler @@ -89,6 +86,20 @@ public class NefFile : ISerializable Script.GetVarSize() + // Script sizeof(uint); // Checksum + /// + /// Parse NefFile from memory + /// + /// Memory + /// Do checksum and MaxItemSize checks + /// NefFile + public static NefFile Parse(ReadOnlyMemory memory, bool verify = true) + { + var reader = new MemoryReader(memory); + var nef = new NefFile(); + nef.Deserialize(ref reader, verify); + return nef; + } + public void Serialize(BinaryWriter writer) { SerializeHeader(writer); @@ -106,18 +117,25 @@ private void SerializeHeader(BinaryWriter writer) writer.WriteFixedString(Compiler, 64); } - public void Deserialize(ref MemoryReader reader) + public void Deserialize(ref MemoryReader reader) => Deserialize(ref reader, true); + + public void Deserialize(ref MemoryReader reader, bool verify = true) { + long startPosition = reader.Position; if (reader.ReadUInt32() != Magic) throw new FormatException("Wrong magic"); Compiler = reader.ReadFixedString(64); Source = reader.ReadVarString(256); if (reader.ReadByte() != 0) throw new FormatException("Reserved bytes must be 0"); Tokens = reader.ReadSerializableArray(128); if (reader.ReadUInt16() != 0) throw new FormatException("Reserved bytes must be 0"); - Script = reader.ReadVarMemory(MaxScriptLength); + Script = reader.ReadVarMemory((int)ExecutionEngineLimits.Default.MaxItemSize); if (Script.Length == 0) throw new ArgumentException($"Script can't be empty"); CheckSum = reader.ReadUInt32(); - if (CheckSum != ComputeChecksum(this)) throw new FormatException("CRC verification fail"); + if (verify) + { + if (CheckSum != ComputeChecksum(this)) throw new FormatException("CRC verification fail"); + if (reader.Position - startPosition > ExecutionEngineLimits.Default.MaxItemSize) throw new FormatException("Max vm item size exceed"); + } } /// diff --git a/src/Neo/SmartContract/NotifyEventArgs.cs b/src/Neo/SmartContract/NotifyEventArgs.cs index 994962f134..257efb3a66 100644 --- a/src/Neo/SmartContract/NotifyEventArgs.cs +++ b/src/Neo/SmartContract/NotifyEventArgs.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// NotifyEventArgs.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -51,10 +52,10 @@ public class NotifyEventArgs : EventArgs, IInteroperable /// The arguments of the event. public NotifyEventArgs(IVerifiable container, UInt160 script_hash, string eventName, Array state) { - this.ScriptContainer = container; - this.ScriptHash = script_hash; - this.EventName = eventName; - this.State = state; + ScriptContainer = container; + ScriptHash = script_hash; + EventName = eventName; + State = state; } public void FromStackItem(StackItem stackItem) @@ -65,11 +66,31 @@ public void FromStackItem(StackItem stackItem) public StackItem ToStackItem(ReferenceCounter referenceCounter) { return new Array(referenceCounter) + { + ScriptHash.ToArray(), + EventName, + State + }; + } + + public StackItem ToStackItem(ReferenceCounter referenceCounter, ApplicationEngine engine) + { + if (engine.IsHardforkEnabled(Hardfork.HF_Domovoi)) { - ScriptHash.ToArray(), - EventName, - State - }; + return new Array(referenceCounter) + { + ScriptHash.ToArray(), + EventName, + State.OnStack ? State : State.DeepCopy(true) + }; + } + + return new Array(referenceCounter) + { + ScriptHash.ToArray(), + EventName, + State + }; } } } diff --git a/src/Neo/SmartContract/StorageContext.cs b/src/Neo/SmartContract/StorageContext.cs index 81222b9810..5c88d1f06c 100644 --- a/src/Neo/SmartContract/StorageContext.cs +++ b/src/Neo/SmartContract/StorageContext.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// StorageContext.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/SmartContract/StorageItem.cs b/src/Neo/SmartContract/StorageItem.cs index b8cfa179e6..133a8fa1dd 100644 --- a/src/Neo/SmartContract/StorageItem.cs +++ b/src/Neo/SmartContract/StorageItem.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// StorageItem.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -36,7 +37,7 @@ public ReadOnlyMemory Value return !value.IsEmpty ? value : value = cache switch { BigInteger bi => bi.ToByteArrayStandard(), - IInteroperable interoperable => BinarySerializer.Serialize(interoperable.ToStackItem(null), 1024 * 1024), + IInteroperable interoperable => BinarySerializer.Serialize(interoperable.ToStackItem(null), ExecutionEngineLimits.Default), null => ReadOnlyMemory.Empty, _ => throw new InvalidCastException() }; @@ -68,7 +69,7 @@ public StorageItem(byte[] value) /// The integer value of the . public StorageItem(BigInteger value) { - this.cache = value; + cache = value; } /// @@ -77,7 +78,7 @@ public StorageItem(BigInteger value) /// The value of the . public StorageItem(IInteroperable interoperable) { - this.cache = interoperable; + cache = interoperable; } /// @@ -144,6 +145,24 @@ public void FromReplica(StorageItem replica) return (T)cache; } + /// + /// Gets an from the storage. + /// + /// Verify deserialization + /// The type of the . + /// The in the storage. + public T GetInteroperable(bool verify = true) where T : IInteroperableVerifiable, new() + { + if (cache is null) + { + var interoperable = new T(); + interoperable.FromStackItem(BinarySerializer.Deserialize(value, ExecutionEngineLimits.Default), verify); + cache = interoperable; + } + value = null; + return (T)cache; + } + public void Serialize(BinaryWriter writer) { writer.Write(Value.Span); @@ -159,6 +178,16 @@ public void Set(BigInteger integer) value = null; } + /// + /// Sets the interoperable value of the storage. + /// + /// The value of the . + public void Set(IInteroperable interoperable) + { + cache = interoperable; + value = null; + } + public static implicit operator BigInteger(StorageItem item) { item.cache ??= new BigInteger(item.value.Span); diff --git a/src/Neo/SmartContract/StorageKey.cs b/src/Neo/SmartContract/StorageKey.cs index 2dc5f49c63..a0136e4456 100644 --- a/src/Neo/SmartContract/StorageKey.cs +++ b/src/Neo/SmartContract/StorageKey.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// StorageKey.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -72,7 +73,7 @@ public byte[] ToArray() { if (cache is null) { - cache = GC.AllocateUninitializedArray(sizeof(int) + Key.Length); + cache = new byte[sizeof(int) + Key.Length]; BinaryPrimitives.WriteInt32LittleEndian(cache, Id); Key.CopyTo(cache.AsMemory(sizeof(int))); } diff --git a/src/Neo/SmartContract/TriggerType.cs b/src/Neo/SmartContract/TriggerType.cs index 94fbfabddc..1e93417968 100644 --- a/src/Neo/SmartContract/TriggerType.cs +++ b/src/Neo/SmartContract/TriggerType.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// TriggerType.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/SmartContract/ValidatorAttribute.cs b/src/Neo/SmartContract/ValidatorAttribute.cs index c5218da4b0..23e785fc52 100644 --- a/src/Neo/SmartContract/ValidatorAttribute.cs +++ b/src/Neo/SmartContract/ValidatorAttribute.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ValidatorAttribute.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/TimeProvider.cs b/src/Neo/TimeProvider.cs index ae999add71..6a4542f9a2 100644 --- a/src/Neo/TimeProvider.cs +++ b/src/Neo/TimeProvider.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// TimeProvider.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/UInt160.cs b/src/Neo/UInt160.cs index 1e005c1113..8dfd6bf70c 100644 --- a/src/Neo/UInt160.cs +++ b/src/Neo/UInt160.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// UInt160.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/UInt256.cs b/src/Neo/UInt256.cs index 718bbdb01c..7c4d996339 100644 --- a/src/Neo/UInt256.cs +++ b/src/Neo/UInt256.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// UInt256.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/VM/Helper.cs b/src/Neo/VM/Helper.cs index 7ff36e1ed5..4a82041ea5 100644 --- a/src/Neo/VM/Helper.cs +++ b/src/Neo/VM/Helper.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// Helper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Wallets/AssetDescriptor.cs b/src/Neo/Wallets/AssetDescriptor.cs index 9885138757..7b9be7b16b 100644 --- a/src/Neo/Wallets/AssetDescriptor.cs +++ b/src/Neo/Wallets/AssetDescriptor.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// AssetDescriptor.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -61,10 +62,10 @@ public AssetDescriptor(DataCache snapshot, ProtocolSettings settings, UInt160 as } using ApplicationEngine engine = ApplicationEngine.Run(script, snapshot, settings: settings, gas: 0_30000000L); if (engine.State != VMState.HALT) throw new ArgumentException(null, nameof(asset_id)); - this.AssetId = asset_id; - this.AssetName = contract.Manifest.Name; - this.Symbol = engine.ResultStack.Pop().GetString(); - this.Decimals = (byte)engine.ResultStack.Pop().GetInteger(); + AssetId = asset_id; + AssetName = contract.Manifest.Name; + Symbol = engine.ResultStack.Pop().GetString(); + Decimals = (byte)engine.ResultStack.Pop().GetInteger(); } public override string ToString() diff --git a/src/Neo/Wallets/Helper.cs b/src/Neo/Wallets/Helper.cs index d1a143e6a5..1273bafc50 100644 --- a/src/Neo/Wallets/Helper.cs +++ b/src/Neo/Wallets/Helper.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// Helper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -12,7 +13,12 @@ using Neo.IO; using Neo.Network.P2P; using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; using System; +using static Neo.SmartContract.Helper; namespace Neo.Wallets { @@ -30,7 +36,7 @@ public static class Helper /// The signature for the . public static byte[] Sign(this IVerifiable verifiable, KeyPair key, uint network) { - return Crypto.Sign(verifiable.GetSignData(network), key.PrivateKey, key.PublicKey.EncodePoint(false)[1..]); + return Crypto.Sign(verifiable.GetSignData(network), key.PrivateKey); } /// @@ -71,5 +77,91 @@ internal static byte[] XOR(byte[] x, byte[] y) r[i] = (byte)(x[i] ^ y[i]); return r; } + + /// + /// Calculates the network fee for the specified transaction. + /// In the unit of datoshi, 1 datoshi = 1e-8 GAS + /// + /// The transaction to calculate. + /// The snapshot used to read data. + /// Thr protocol settings to use. + /// Function to retrive the script's account from a hash. + /// The maximum cost that can be spent when a contract is executed. + /// The network fee of the transaction. + public static long CalculateNetworkFee(this Transaction tx, DataCache snapshot, ProtocolSettings settings, Func accountScript, long maxExecutionCost = ApplicationEngine.TestModeGas) + { + UInt160[] hashes = tx.GetScriptHashesForVerifying(snapshot); + + // base size for transaction: includes const_header + signers + attributes + script + hashes + int size = Transaction.HeaderSize + tx.Signers.GetVarSize() + tx.Attributes.GetVarSize() + tx.Script.GetVarSize() + IO.Helper.GetVarSize(hashes.Length), index = -1; + uint exec_fee_factor = NativeContract.Policy.GetExecFeeFactor(snapshot); + long networkFee = 0; + foreach (UInt160 hash in hashes) + { + index++; + byte[] witnessScript = accountScript(hash); + byte[] invocationScript = null; + + if (tx.Witnesses != null && witnessScript is null) + { + // Try to find the script in the witnesses + Witness witness = tx.Witnesses[index]; + witnessScript = witness?.VerificationScript.ToArray(); + + if (witnessScript is null || witnessScript.Length == 0) + { + // Then it's a contract-based witness, so try to get the corresponding invocation script for it + invocationScript = witness?.InvocationScript.ToArray(); + } + } + + if (witnessScript is null || witnessScript.Length == 0) + { + var contract = NativeContract.ContractManagement.GetContract(snapshot, hash); + if (contract is null) + throw new ArgumentException($"The smart contract or address {hash} is not found"); + var md = contract.Manifest.Abi.GetMethod(ContractBasicMethod.Verify, ContractBasicMethod.VerifyPCount); + if (md is null) + throw new ArgumentException($"The smart contract {contract.Hash} haven't got verify method"); + if (md.ReturnType != ContractParameterType.Boolean) + throw new ArgumentException("The verify method doesn't return boolean value."); + if (md.Parameters.Length > 0 && invocationScript is null) + throw new ArgumentException("The verify method requires parameters that need to be passed via the witness' invocation script."); + + // Empty verification and non-empty invocation scripts + var invSize = invocationScript?.GetVarSize() ?? Array.Empty().GetVarSize(); + size += Array.Empty().GetVarSize() + invSize; + + // Check verify cost + using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot.CreateSnapshot(), settings: settings, gas: maxExecutionCost); + engine.LoadContract(contract, md, CallFlags.ReadOnly); + if (invocationScript != null) engine.LoadScript(invocationScript, configureState: p => p.CallFlags = CallFlags.None); + if (engine.Execute() == VMState.FAULT) throw new ArgumentException($"Smart contract {contract.Hash} verification fault."); + if (!engine.ResultStack.Pop().GetBoolean()) throw new ArgumentException($"Smart contract {contract.Hash} returns false."); + + maxExecutionCost -= engine.FeeConsumed; + if (maxExecutionCost <= 0) throw new InvalidOperationException("Insufficient GAS."); + networkFee += engine.FeeConsumed; + } + else if (IsSignatureContract(witnessScript)) + { + size += 67 + witnessScript.GetVarSize(); + networkFee += exec_fee_factor * SignatureContractCost(); + } + else if (IsMultiSigContract(witnessScript, out int m, out int n)) + { + int size_inv = 66 * m; + size += IO.Helper.GetVarSize(size_inv) + size_inv + witnessScript.GetVarSize(); + networkFee += exec_fee_factor * MultiSignatureContractCost(m, n); + } + // We can support more contract types in the future. + } + networkFee += size * NativeContract.Policy.GetFeePerByte(snapshot); + foreach (TransactionAttribute attr in tx.Attributes) + { + networkFee += attr.CalculateNetworkFee(snapshot, tx); + } + return networkFee; + } } } diff --git a/src/Neo/Wallets/IWalletFactory.cs b/src/Neo/Wallets/IWalletFactory.cs index 54409712f1..5c69b56262 100644 --- a/src/Neo/Wallets/IWalletFactory.cs +++ b/src/Neo/Wallets/IWalletFactory.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// IWalletFactory.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Wallets/IWalletProvider.cs b/src/Neo/Wallets/IWalletProvider.cs index 6f3ff593d8..a4edc17692 100644 --- a/src/Neo/Wallets/IWalletProvider.cs +++ b/src/Neo/Wallets/IWalletProvider.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// IWalletProvider.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Wallets/KeyPair.cs b/src/Neo/Wallets/KeyPair.cs index f7566408cd..7b15c0bbd0 100644 --- a/src/Neo/Wallets/KeyPair.cs +++ b/src/Neo/Wallets/KeyPair.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// KeyPair.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -47,14 +48,14 @@ public KeyPair(byte[] privateKey) { if (privateKey.Length != 32 && privateKey.Length != 96 && privateKey.Length != 104) throw new ArgumentException(null, nameof(privateKey)); - this.PrivateKey = privateKey[^32..]; + PrivateKey = privateKey[^32..]; if (privateKey.Length == 32) { - this.PublicKey = Cryptography.ECC.ECCurve.Secp256r1.G * privateKey; + PublicKey = Cryptography.ECC.ECCurve.Secp256r1.G * privateKey; } else { - this.PublicKey = Cryptography.ECC.ECPoint.FromBytes(privateKey, Cryptography.ECC.ECCurve.Secp256r1); + PublicKey = Cryptography.ECC.ECPoint.FromBytes(privateKey, Cryptography.ECC.ECCurve.Secp256r1); } } diff --git a/src/Neo/Wallets/NEP6/NEP6Account.cs b/src/Neo/Wallets/NEP6/NEP6Account.cs index 55bbc603c2..7998b9ea29 100644 --- a/src/Neo/Wallets/NEP6/NEP6Account.cs +++ b/src/Neo/Wallets/NEP6/NEP6Account.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// NEP6Account.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Wallets/NEP6/NEP6Contract.cs b/src/Neo/Wallets/NEP6/NEP6Contract.cs index 289ef4387c..cecc80be7e 100644 --- a/src/Neo/Wallets/NEP6/NEP6Contract.cs +++ b/src/Neo/Wallets/NEP6/NEP6Contract.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// NEP6Contract.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Wallets/NEP6/NEP6Wallet.cs b/src/Neo/Wallets/NEP6/NEP6Wallet.cs index 5a22f5f32a..17a59fc830 100644 --- a/src/Neo/Wallets/NEP6/NEP6Wallet.cs +++ b/src/Neo/Wallets/NEP6/NEP6Wallet.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// NEP6Wallet.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -14,6 +15,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; @@ -62,10 +64,10 @@ public NEP6Wallet(string path, string password, ProtocolSettings settings, strin else { this.name = name; - this.version = Version.Parse("1.0"); - this.Scrypt = ScryptParameters.Default; - this.accounts = new Dictionary(); - this.extra = JToken.Null; + version = Version.Parse("1.0"); + Scrypt = ScryptParameters.Default; + accounts = new Dictionary(); + extra = JToken.Null; } } @@ -84,8 +86,8 @@ public NEP6Wallet(string path, string password, ProtocolSettings settings, JObje private void LoadFromJson(JObject wallet, out ScryptParameters scrypt, out Dictionary accounts, out JToken extra) { - this.version = Version.Parse(wallet["version"].AsString()); - this.name = wallet["name"]?.AsString(); + version = Version.Parse(wallet["version"].AsString()); + name = wallet["name"]?.AsString(); scrypt = ScryptParameters.FromJson((JObject)wallet["scrypt"]); accounts = ((JArray)wallet["accounts"]).Select(p => NEP6Account.FromJson((JObject)p, this)).ToDictionary(p => p.ScriptHash); extra = wallet["extra"]; @@ -222,6 +224,10 @@ public override IEnumerable GetAccounts() public override WalletAccount Import(X509Certificate2 cert) { + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + throw new PlatformNotSupportedException("Importing certificates is not supported on macOS."); + } KeyPair key; using (ECDsa ecdsa = cert.GetECDsaPrivateKey()) { diff --git a/src/Neo/Wallets/NEP6/NEP6WalletFactory.cs b/src/Neo/Wallets/NEP6/NEP6WalletFactory.cs index 75cf0c7cf8..759ab98de6 100644 --- a/src/Neo/Wallets/NEP6/NEP6WalletFactory.cs +++ b/src/Neo/Wallets/NEP6/NEP6WalletFactory.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// NEP6WalletFactory.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Wallets/NEP6/ScryptParameters.cs b/src/Neo/Wallets/NEP6/ScryptParameters.cs index aaa6e04b89..d8e076272a 100644 --- a/src/Neo/Wallets/NEP6/ScryptParameters.cs +++ b/src/Neo/Wallets/NEP6/ScryptParameters.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// ScryptParameters.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -45,9 +46,9 @@ public class ScryptParameters /// Parallelization parameter. public ScryptParameters(int n, int r, int p) { - this.N = n; - this.R = r; - this.P = p; + N = n; + R = r; + P = p; } /// diff --git a/src/Neo/Wallets/TransferOutput.cs b/src/Neo/Wallets/TransferOutput.cs index d562062a64..7f7d7777a1 100644 --- a/src/Neo/Wallets/TransferOutput.cs +++ b/src/Neo/Wallets/TransferOutput.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// TransferOutput.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. diff --git a/src/Neo/Wallets/Wallet.cs b/src/Neo/Wallets/Wallet.cs index 60a3b60891..aca30b008f 100644 --- a/src/Neo/Wallets/Wallet.cs +++ b/src/Neo/Wallets/Wallet.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// Wallet.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -21,6 +22,7 @@ using System.Collections.Generic; using System.Linq; using System.Numerics; +using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; @@ -126,8 +128,8 @@ public abstract class Wallet /// The to be used by the wallet. protected Wallet(string path, ProtocolSettings settings) { - this.ProtocolSettings = settings; - this.Path = path; + ProtocolSettings = settings; + Path = path; } /// @@ -136,24 +138,26 @@ protected Wallet(string path, ProtocolSettings settings) /// The created account. public WalletAccount CreateAccount() { - byte[] privateKey = new byte[32]; - generate: - try + var privateKey = new byte[32]; + using var rng = RandomNumberGenerator.Create(); + + do { - using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) + try { rng.GetBytes(privateKey); + return CreateAccount(privateKey); + } + catch (ArgumentException) + { + // Try again + } + finally + { + Array.Clear(privateKey, 0, privateKey.Length); } - return CreateAccount(privateKey); - } - catch (ArgumentException) - { - goto generate; - } - finally - { - Array.Clear(privateKey, 0, privateKey.Length); } + while (true); } /// @@ -171,7 +175,7 @@ public WalletAccount CreateAccount(Contract contract, byte[] privateKey) private static List<(UInt160 Account, BigInteger Value)> FindPayingAccounts(List<(UInt160 Account, BigInteger Value)> orderedAccounts, BigInteger amount) { var result = new List<(UInt160 Account, BigInteger Value)>(); - BigInteger sum_balance = orderedAccounts.Select(p => p.Value).Sum(); + var sum_balance = orderedAccounts.Select(p => p.Value).Sum(); if (sum_balance == amount) { result.AddRange(orderedAccounts); @@ -409,6 +413,10 @@ private static Signer[] GetSigners(UInt160 sender, Signer[] cosigners) /// The imported account. public virtual WalletAccount Import(X509Certificate2 cert) { + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + throw new PlatformNotSupportedException("Importing certificates is not supported on macOS."); + } byte[] privateKey; using (ECDsa ecdsa = cert.GetECDsaPrivateKey()) { @@ -532,7 +540,7 @@ public Transaction MakeTransaction(DataCache snapshot, TransferOutput[] outputs, /// The sender of the transaction. /// The cosigners to be added to the transaction. /// The attributes to be added to the transaction. - /// The maximum gas that can be spent to execute the script. + /// The maximum gas that can be spent to execute the script, in the unit of datoshi, 1 datoshi = 1e-8 GAS. /// The block environment to execute the transaction. If null, will be used. /// The created transaction. public Transaction MakeTransaction(DataCache snapshot, ReadOnlyMemory script, UInt160 sender = null, Signer[] cosigners = null, TransactionAttribute[] attributes = null, long maxGas = ApplicationEngine.TestModeGas, Block persistingBlock = null) @@ -572,98 +580,15 @@ private Transaction MakeTransaction(DataCache snapshot, ReadOnlyMemory scr { throw new InvalidOperationException($"Failed execution for '{Convert.ToBase64String(script.Span)}'", engine.FaultException); } - tx.SystemFee = engine.GasConsumed; + tx.SystemFee = engine.FeeConsumed; } - tx.NetworkFee = CalculateNetworkFee(snapshot, tx); + tx.NetworkFee = tx.CalculateNetworkFee(snapshot, ProtocolSettings, (a) => GetAccount(a)?.Contract?.Script, maxGas); if (value >= tx.SystemFee + tx.NetworkFee) return tx; } throw new InvalidOperationException("Insufficient GAS"); } - /// - /// Calculates the network fee for the specified transaction. - /// - /// The snapshot used to read data. - /// The transaction to calculate. - /// The network fee of the transaction. - public long CalculateNetworkFee(DataCache snapshot, Transaction tx) - { - UInt160[] hashes = tx.GetScriptHashesForVerifying(snapshot); - - // base size for transaction: includes const_header + signers + attributes + script + hashes - int size = Transaction.HeaderSize + tx.Signers.GetVarSize() + tx.Attributes.GetVarSize() + tx.Script.GetVarSize() + IO.Helper.GetVarSize(hashes.Length); - uint exec_fee_factor = NativeContract.Policy.GetExecFeeFactor(snapshot); - long networkFee = 0; - int index = -1; - foreach (UInt160 hash in hashes) - { - index++; - byte[] witness_script = GetAccount(hash)?.Contract?.Script; - byte[] invocationScript = null; - - if (tx.Witnesses != null) - { - if (witness_script is null) - { - // Try to find the script in the witnesses - Witness witness = tx.Witnesses[index]; - witness_script = witness?.VerificationScript.ToArray(); - - if (witness_script is null || witness_script.Length == 0) - { - // Then it's a contract-based witness, so try to get the corresponding invocation script for it - invocationScript = witness?.InvocationScript.ToArray(); - } - } - } - - if (witness_script is null || witness_script.Length == 0) - { - var contract = NativeContract.ContractManagement.GetContract(snapshot, hash); - if (contract is null) - throw new ArgumentException($"The smart contract or address {hash} is not found"); - var md = contract.Manifest.Abi.GetMethod("verify", -1); - if (md is null) - throw new ArgumentException($"The smart contract {contract.Hash} haven't got verify method"); - if (md.ReturnType != ContractParameterType.Boolean) - throw new ArgumentException("The verify method doesn't return boolean value."); - if (md.Parameters.Length > 0 && invocationScript is null) - throw new ArgumentException("The verify method requires parameters that need to be passed via the witness' invocation script."); - - // Empty verification and non-empty invocation scripts - var invSize = invocationScript?.GetVarSize() ?? Array.Empty().GetVarSize(); - size += Array.Empty().GetVarSize() + invSize; - - // Check verify cost - using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot.CreateSnapshot(), settings: ProtocolSettings); - engine.LoadContract(contract, md, CallFlags.ReadOnly); - if (invocationScript != null) engine.LoadScript(invocationScript, configureState: p => p.CallFlags = CallFlags.None); - if (engine.Execute() == VMState.FAULT) throw new ArgumentException($"Smart contract {contract.Hash} verification fault."); - if (!engine.ResultStack.Pop().GetBoolean()) throw new ArgumentException($"Smart contract {contract.Hash} returns false."); - - networkFee += engine.GasConsumed; - } - else if (IsSignatureContract(witness_script)) - { - size += 67 + witness_script.GetVarSize(); - networkFee += exec_fee_factor * SignatureContractCost(); - } - else if (IsMultiSigContract(witness_script, out int m, out int n)) - { - int size_inv = 66 * m; - size += IO.Helper.GetVarSize(size_inv) + size_inv + witness_script.GetVarSize(); - networkFee += exec_fee_factor * MultiSignatureContractCost(m, n); - } - else - { - //We can support more contract types in the future. - } - } - networkFee += size * NativeContract.Policy.GetFeePerByte(snapshot); - return networkFee; - } - /// /// Signs the in the specified with the wallet. /// diff --git a/src/Neo/Wallets/WalletAccount.cs b/src/Neo/Wallets/WalletAccount.cs index 9f03beea4f..62bcd85ec1 100644 --- a/src/Neo/Wallets/WalletAccount.cs +++ b/src/Neo/Wallets/WalletAccount.cs @@ -1,10 +1,11 @@ -// Copyright (C) 2015-2022 The Neo Project. -// -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// Copyright (C) 2015-2024 The Neo Project. +// +// WalletAccount.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. -// +// // Redistribution and use in source and binary forms with or without // modifications are permitted. @@ -75,8 +76,8 @@ public abstract class WalletAccount /// The to be used by the wallet. protected WalletAccount(UInt160 scriptHash, ProtocolSettings settings) { - this.ProtocolSettings = settings; - this.ScriptHash = scriptHash; + ProtocolSettings = settings; + ScriptHash = scriptHash; } } } diff --git a/src/Plugins/ApplicationLogs/ApplicationLogs.csproj b/src/Plugins/ApplicationLogs/ApplicationLogs.csproj new file mode 100644 index 0000000000..0d01281c6a --- /dev/null +++ b/src/Plugins/ApplicationLogs/ApplicationLogs.csproj @@ -0,0 +1,22 @@ + + + net8.0 + Neo.Plugins.ApplicationLogs + Neo.Plugins + enable + ../../../bin/$(PackageId) + + + + + + false + runtime + + + + + PreserveNewest + + + diff --git a/src/Plugins/ApplicationLogs/ApplicationLogs.json b/src/Plugins/ApplicationLogs/ApplicationLogs.json new file mode 100644 index 0000000000..af601bc81e --- /dev/null +++ b/src/Plugins/ApplicationLogs/ApplicationLogs.json @@ -0,0 +1,11 @@ +{ + "PluginConfiguration": { + "Path": "ApplicationLogs_{0}", + "Network": 860833102, + "MaxStackSize": 65535, + "Debug": false + }, + "Dependency": [ + "RpcServer" + ] +} diff --git a/src/Plugins/ApplicationLogs/LogReader.cs b/src/Plugins/ApplicationLogs/LogReader.cs new file mode 100644 index 0000000000..6a8682ab5e --- /dev/null +++ b/src/Plugins/ApplicationLogs/LogReader.cs @@ -0,0 +1,459 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// LogReader.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.ConsoleService; +using Neo.Json; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.Plugins.ApplicationLogs.Store; +using Neo.Plugins.ApplicationLogs.Store.Models; +using Neo.Plugins.RpcServer; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using System.Numerics; +using static System.IO.Path; + +namespace Neo.Plugins.ApplicationLogs +{ + public class LogReader : Plugin + { + #region Globals + + private NeoStore _neostore; + private NeoSystem _neosystem; + private readonly List _logEvents; + + #endregion + + public override string Name => "ApplicationLogs"; + public override string Description => "Synchronizes smart contract VM executions and notifications (NotifyLog) on blockchain."; + + #region Ctor + + public LogReader() + { + _logEvents = new(); + Blockchain.Committing += OnCommitting; + Blockchain.Committed += OnCommitted; + } + + #endregion + + #region Override Methods + + public override string ConfigFile => Combine(RootPath, "ApplicationLogs.json"); + + public override void Dispose() + { + Blockchain.Committing -= OnCommitting; + Blockchain.Committed -= OnCommitted; + if (Settings.Default.Debug) + ApplicationEngine.Log -= OnApplicationEngineLog; + GC.SuppressFinalize(this); + } + + protected override void Configure() + { + Settings.Load(GetConfiguration()); + } + + protected override void OnSystemLoaded(NeoSystem system) + { + if (system.Settings.Network != Settings.Default.Network) + return; + string path = string.Format(Settings.Default.Path, Settings.Default.Network.ToString("X8")); + var store = system.LoadStore(GetFullPath(path)); + _neostore = new NeoStore(store); + _neosystem = system; + RpcServerPlugin.RegisterMethods(this, Settings.Default.Network); + + if (Settings.Default.Debug) + ApplicationEngine.Log += OnApplicationEngineLog; + } + + #endregion + + #region JSON RPC Methods + + [RpcMethod] + public JToken GetApplicationLog(JArray _params) + { + if (_params == null || _params.Count == 0) + throw new RpcException(RpcError.InvalidParams); + if (UInt256.TryParse(_params[0].AsString(), out var hash)) + { + var raw = BlockToJObject(hash); + if (raw == null) + raw = TransactionToJObject(hash); + if (raw == null) + throw new RpcException(RpcError.InvalidParams.WithData("Unknown transaction/blockhash")); + + if (_params.Count >= 2 && Enum.TryParse(_params[1].AsString(), true, out TriggerType triggerType)) + { + var executions = raw["executions"] as JArray; + for (int i = 0; i < executions.Count;) + { + if (executions[i]["trigger"].AsString().Equals(triggerType.ToString(), StringComparison.OrdinalIgnoreCase) == false) + executions.RemoveAt(i); + else + i++; + } + } + + return raw ?? JToken.Null; + } + else + throw new RpcException(RpcError.InvalidParams); + } + + #endregion + + #region Console Commands + + [ConsoleCommand("log block", Category = "ApplicationLog Commands")] + private void OnGetBlockCommand(string blockHashOrIndex, string eventName = null) + { + UInt256 blockhash; + if (uint.TryParse(blockHashOrIndex, out var blockIndex)) + { + blockhash = NativeContract.Ledger.GetBlockHash(_neosystem.StoreView, blockIndex); + } + else if (UInt256.TryParse(blockHashOrIndex, out blockhash) == false) + { + ConsoleHelper.Error("Invalid block hash or index."); + return; + } + + var blockOnPersist = string.IsNullOrEmpty(eventName) ? + _neostore.GetBlockLog(blockhash, TriggerType.OnPersist) : + _neostore.GetBlockLog(blockhash, TriggerType.OnPersist, eventName); + var blockPostPersist = string.IsNullOrEmpty(eventName) ? + _neostore.GetBlockLog(blockhash, TriggerType.PostPersist) : + _neostore.GetBlockLog(blockhash, TriggerType.PostPersist, eventName); + + if (blockOnPersist == null && blockOnPersist == null) + ConsoleHelper.Error($"No logs."); + if (blockOnPersist != null) + PrintExecutionToConsole(blockOnPersist); + if (blockPostPersist != null) + { + ConsoleHelper.Info("--------------------------------"); + PrintExecutionToConsole(blockPostPersist); + } + } + + [ConsoleCommand("log tx", Category = "ApplicationLog Commands")] + private void OnGetTransactionCommand(UInt256 txhash, string eventName = null) + { + var txApplication = string.IsNullOrEmpty(eventName) ? + _neostore.GetTransactionLog(txhash) : + _neostore.GetTransactionLog(txhash, eventName); + + if (txApplication == null) + ConsoleHelper.Error($"No logs."); + else + PrintExecutionToConsole(txApplication); + } + + [ConsoleCommand("log contract", Category = "ApplicationLog Commands")] + private void OnGetContractCommand(UInt160 scripthash, uint page = 1, uint pageSize = 1, string eventName = null) + { + if (page == 0) + { + ConsoleHelper.Error("Page is invalid. Pick a number 1 and above."); + return; + } + + if (pageSize == 0) + { + ConsoleHelper.Error("PageSize is invalid. Pick a number between 1 and 10."); + return; + } + + var txContract = string.IsNullOrEmpty(eventName) ? + _neostore.GetContractLog(scripthash, TriggerType.Application, page, pageSize) : + _neostore.GetContractLog(scripthash, TriggerType.Application, eventName, page, pageSize); + + if (txContract.Count == 0) + ConsoleHelper.Error($"No logs."); + else + PrintEventModelToConsole(txContract); + } + + + #endregion + + #region Blockchain Events + + private void OnCommitting(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) + { + if (system.Settings.Network != Settings.Default.Network) + return; + + if (_neostore is null) + return; + _neostore.StartBlockLogBatch(); + _neostore.PutBlockLog(block, applicationExecutedList); + if (Settings.Default.Debug) + { + foreach (var appEng in applicationExecutedList.Where(w => w.Transaction != null)) + { + var logs = _logEvents.Where(w => w.ScriptContainer.Hash == appEng.Transaction.Hash).ToList(); + if (logs.Any()) + _neostore.PutTransactionEngineLogState(appEng.Transaction.Hash, logs); + } + _logEvents.Clear(); + } + } + + private void OnCommitted(NeoSystem system, Block block) + { + if (system.Settings.Network != Settings.Default.Network) + return; + if (_neostore is null) + return; + _neostore.CommitBlockLog(); + } + + private void OnApplicationEngineLog(object sender, LogEventArgs e) + { + if (Settings.Default.Debug == false) + return; + + if (_neosystem.Settings.Network != Settings.Default.Network) + return; + + if (e.ScriptContainer == null) + return; + + _logEvents.Add(e); + } + + #endregion + + #region Private Methods + + private void PrintExecutionToConsole(BlockchainExecutionModel model) + { + ConsoleHelper.Info("Trigger: ", $"{model.Trigger}"); + ConsoleHelper.Info("VM State: ", $"{model.VmState}"); + if (string.IsNullOrEmpty(model.Exception) == false) + ConsoleHelper.Error($"Exception: {model.Exception}"); + else + ConsoleHelper.Info("Exception: ", "null"); + ConsoleHelper.Info("Gas Consumed: ", $"{new BigDecimal((BigInteger)model.GasConsumed, NativeContract.GAS.Decimals)}"); + if (model.Stack.Length == 0) + ConsoleHelper.Info("Stack: ", "[]"); + else + { + ConsoleHelper.Info("Stack: "); + for (int i = 0; i < model.Stack.Length; i++) + ConsoleHelper.Info($" {i}: ", $"{model.Stack[i].ToJson()}"); + } + if (model.Notifications.Length == 0) + ConsoleHelper.Info("Notifications: ", "[]"); + else + { + ConsoleHelper.Info("Notifications:"); + foreach (var notifyItem in model.Notifications) + { + ConsoleHelper.Info(); + ConsoleHelper.Info(" ScriptHash: ", $"{notifyItem.ScriptHash}"); + ConsoleHelper.Info(" Event Name: ", $"{notifyItem.EventName}"); + ConsoleHelper.Info(" State Parameters:"); + for (int i = 0; i < notifyItem.State.Length; i++) + ConsoleHelper.Info($" {GetMethodParameterName(notifyItem.ScriptHash, notifyItem.EventName, i)}: ", $"{notifyItem.State[i].ToJson()}"); + } + } + if (Settings.Default.Debug) + { + if (model.Logs.Length == 0) + ConsoleHelper.Info("Logs: ", "[]"); + else + { + ConsoleHelper.Info("Logs:"); + foreach (var logItem in model.Logs) + { + ConsoleHelper.Info(); + ConsoleHelper.Info(" ScriptHash: ", $"{logItem.ScriptHash}"); + ConsoleHelper.Info(" Message: ", $"{logItem.Message}"); + } + } + } + } + + private void PrintEventModelToConsole(IReadOnlyCollection<(BlockchainEventModel NotifyLog, UInt256 TxHash)> models) + { + foreach (var (notifyItem, txhash) in models) + { + ConsoleHelper.Info("Transaction Hash: ", $"{txhash}"); + ConsoleHelper.Info(); + ConsoleHelper.Info(" Event Name: ", $"{notifyItem.EventName}"); + ConsoleHelper.Info(" State Parameters:"); + for (int i = 0; i < notifyItem.State.Length; i++) + ConsoleHelper.Info($" {GetMethodParameterName(notifyItem.ScriptHash, notifyItem.EventName, i)}: ", $"{notifyItem.State[i].ToJson()}"); + ConsoleHelper.Info("--------------------------------"); + } + } + + private string GetMethodParameterName(UInt160 scriptHash, string methodName, int parameterIndex) + { + var contract = NativeContract.ContractManagement.GetContract(_neosystem.StoreView, scriptHash); + if (contract == null) + return $"{parameterIndex}"; + var contractEvent = contract.Manifest.Abi.Events.SingleOrDefault(s => s.Name == methodName); + return contractEvent.Parameters[parameterIndex].Name; + } + + private JObject EventModelToJObject(BlockchainEventModel model) + { + var root = new JObject(); + root["contract"] = model.ScriptHash.ToString(); + root["eventname"] = model.EventName; + root["state"] = model.State.Select(s => s.ToJson()).ToArray(); + return root; + } + + private JObject TransactionToJObject(UInt256 txHash) + { + var appLog = _neostore.GetTransactionLog(txHash); + if (appLog == null) + return null; + + var raw = new JObject(); + raw["txid"] = txHash.ToString(); + + var trigger = new JObject(); + trigger["trigger"] = appLog.Trigger; + trigger["vmstate"] = appLog.VmState; + trigger["exception"] = string.IsNullOrEmpty(appLog.Exception) ? null : appLog.Exception; + trigger["gasconsumed"] = appLog.GasConsumed.ToString(); + + try + { + trigger["stack"] = appLog.Stack.Select(s => s.ToJson(Settings.Default.MaxStackSize)).ToArray(); + } + catch (Exception ex) + { + trigger["exception"] = ex.Message; + } + + trigger["notifications"] = appLog.Notifications.Select(s => + { + var notification = new JObject(); + notification["contract"] = s.ScriptHash.ToString(); + notification["eventname"] = s.EventName; + + try + { + var state = new JObject(); + state["type"] = "Array"; + state["value"] = s.State.Select(ss => ss.ToJson()).ToArray(); + + notification["state"] = state; + } + catch (InvalidOperationException) + { + notification["state"] = "error: recursive reference"; + } + + return notification; + }).ToArray(); + + if (Settings.Default.Debug) + { + trigger["logs"] = appLog.Logs.Select(s => + { + var log = new JObject(); + log["contract"] = s.ScriptHash.ToString(); + log["message"] = s.Message; + return log; + }).ToArray(); + } + + raw["executions"] = new[] { trigger }; + return raw; + } + + private JObject BlockToJObject(UInt256 blockHash) + { + var blockOnPersist = _neostore.GetBlockLog(blockHash, TriggerType.OnPersist); + var blockPostPersist = _neostore.GetBlockLog(blockHash, TriggerType.PostPersist); + + if (blockOnPersist == null && blockPostPersist == null) + return null; + + var blockJson = new JObject(); + blockJson["blockhash"] = blockHash.ToString(); + var triggerList = new List(); + + if (blockOnPersist != null) + triggerList.Add(BlockItemToJObject(blockOnPersist)); + if (blockPostPersist != null) + triggerList.Add(BlockItemToJObject(blockPostPersist)); + + blockJson["executions"] = triggerList.ToArray(); + return blockJson; + } + + private JObject BlockItemToJObject(BlockchainExecutionModel blockExecutionModel) + { + JObject trigger = new(); + trigger["trigger"] = blockExecutionModel.Trigger; + trigger["vmstate"] = blockExecutionModel.VmState; + trigger["gasconsumed"] = blockExecutionModel.GasConsumed.ToString(); + try + { + trigger["stack"] = blockExecutionModel.Stack.Select(q => q.ToJson(Settings.Default.MaxStackSize)).ToArray(); + } + catch (Exception ex) + { + trigger["exception"] = ex.Message; + } + trigger["notifications"] = blockExecutionModel.Notifications.Select(s => + { + JObject notification = new(); + notification["contract"] = s.ScriptHash.ToString(); + notification["eventname"] = s.EventName; + try + { + var state = new JObject(); + state["type"] = "Array"; + state["value"] = s.State.Select(ss => ss.ToJson()).ToArray(); + + notification["state"] = state; + } + catch (InvalidOperationException) + { + notification["state"] = "error: recursive reference"; + } + return notification; + }).ToArray(); + + if (Settings.Default.Debug) + { + trigger["logs"] = blockExecutionModel.Logs.Select(s => + { + var log = new JObject(); + log["contract"] = s.ScriptHash.ToString(); + log["message"] = s.Message; + return log; + }).ToArray(); + } + + return trigger; + } + + #endregion + } +} diff --git a/src/Plugins/ApplicationLogs/Settings.cs b/src/Plugins/ApplicationLogs/Settings.cs new file mode 100644 index 0000000000..8f2a0da1e1 --- /dev/null +++ b/src/Plugins/ApplicationLogs/Settings.cs @@ -0,0 +1,39 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Settings.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.Extensions.Configuration; + +namespace Neo.Plugins.ApplicationLogs +{ + internal class Settings + { + public string Path { get; } + public uint Network { get; } + public int MaxStackSize { get; } + + public bool Debug { get; } + + public static Settings Default { get; private set; } + + private Settings(IConfigurationSection section) + { + Path = section.GetValue("Path", "ApplicationLogs_{0}"); + Network = section.GetValue("Network", 5195086u); + MaxStackSize = section.GetValue("MaxStackSize", (int)ushort.MaxValue); + Debug = section.GetValue("Debug", false); + } + + public static void Load(IConfigurationSection section) + { + Default = new Settings(section); + } + } +} diff --git a/src/Plugins/ApplicationLogs/Store/LogStorageStore.cs b/src/Plugins/ApplicationLogs/Store/LogStorageStore.cs new file mode 100644 index 0000000000..147a80034b --- /dev/null +++ b/src/Plugins/ApplicationLogs/Store/LogStorageStore.cs @@ -0,0 +1,412 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// LogStorageStore.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using Neo.Persistence; +using Neo.Plugins.ApplicationLogs.Store.States; +using Neo.SmartContract; +using Neo.VM; +using Neo.VM.Types; + +namespace Neo.Plugins.ApplicationLogs.Store +{ + public sealed class LogStorageStore : IDisposable + { + #region Prefixes + + private static readonly int Prefix_Size = sizeof(int) + sizeof(byte); + private static readonly int Prefix_Block_Trigger_Size = Prefix_Size + UInt256.Length; + private static readonly int Prefix_Execution_Block_Trigger_Size = Prefix_Size + UInt256.Length; + + private static readonly int Prefix_Id = 0x414c4f47; // Magic Code: (ALOG); + private static readonly byte Prefix_Engine = 0x18; // Engine_GUID -> ScriptHash, Message + private static readonly byte Prefix_Engine_Transaction = 0x19; // TxHash -> Engine_GUID_List + private static readonly byte Prefix_Block = 0x20; // BlockHash, Trigger -> NotifyLog_GUID_List + private static readonly byte Prefix_Notify = 0x21; // NotifyLog_GUID -> ScriptHash, EventName, StackItem_GUID_List + private static readonly byte Prefix_Contract = 0x22; // ScriptHash, TimeStamp, EventIterIndex -> txHash, Trigger, NotifyLog_GUID + private static readonly byte Prefix_Execution = 0x23; // Execution_GUID -> Data, StackItem_GUID_List + private static readonly byte Prefix_Execution_Block = 0x24; // BlockHash, Trigger -> Execution_GUID + private static readonly byte Prefix_Execution_Transaction = 0x25; // TxHash -> Execution_GUID + private static readonly byte Prefix_Transaction = 0x26; // TxHash -> NotifyLog_GUID_List + private static readonly byte Prefix_StackItem = 0xed; // StackItem_GUID -> Data + + #endregion + + #region Global Variables + + private readonly ISnapshot _snapshot; + + #endregion + + #region Ctor + + public LogStorageStore(ISnapshot snapshot) + { + ArgumentNullException.ThrowIfNull(snapshot, nameof(snapshot)); + _snapshot = snapshot; + } + + #endregion + + #region IDisposable + + public void Dispose() + { + GC.SuppressFinalize(this); + } + + #endregion + + #region Put + + public Guid PutEngineState(EngineLogState state) + { + var id = Guid.NewGuid(); + var key = new KeyBuilder(Prefix_Id, Prefix_Engine) + .Add(id.ToByteArray()) + .ToArray(); + _snapshot.Put(key, state.ToArray()); + return id; + } + + public void PutTransactionEngineState(UInt256 hash, TransactionEngineLogState state) + { + var key = new KeyBuilder(Prefix_Id, Prefix_Engine_Transaction) + .Add(hash) + .ToArray(); + _snapshot.Put(key, state.ToArray()); + } + + public void PutBlockState(UInt256 hash, TriggerType trigger, BlockLogState state) + { + var key = new KeyBuilder(Prefix_Id, Prefix_Block) + .Add(hash) + .Add((byte)trigger) + .ToArray(); + _snapshot.Put(key, state.ToArray()); + } + + public Guid PutNotifyState(NotifyLogState state) + { + var id = Guid.NewGuid(); + var key = new KeyBuilder(Prefix_Id, Prefix_Notify) + .Add(id.ToByteArray()) + .ToArray(); + _snapshot.Put(key, state.ToArray()); + return id; + } + + public void PutContractState(UInt160 scriptHash, ulong timestamp, uint iterIndex, ContractLogState state) + { + var key = new KeyBuilder(Prefix_Id, Prefix_Contract) + .Add(scriptHash) + .AddBigEndian(timestamp) + .AddBigEndian(iterIndex) + .ToArray(); + _snapshot.Put(key, state.ToArray()); + } + + public Guid PutExecutionState(ExecutionLogState state) + { + var id = Guid.NewGuid(); + var key = new KeyBuilder(Prefix_Id, Prefix_Execution) + .Add(id.ToByteArray()) + .ToArray(); + _snapshot.Put(key, state.ToArray()); + return id; + } + + public void PutExecutionBlockState(UInt256 blockHash, TriggerType trigger, Guid executionStateId) + { + var key = new KeyBuilder(Prefix_Id, Prefix_Execution_Block) + .Add(blockHash) + .Add((byte)trigger) + .ToArray(); + _snapshot.Put(key, executionStateId.ToByteArray()); + } + + public void PutExecutionTransactionState(UInt256 txHash, Guid executionStateId) + { + var key = new KeyBuilder(Prefix_Id, Prefix_Execution_Transaction) + .Add(txHash) + .ToArray(); + _snapshot.Put(key, executionStateId.ToByteArray()); + } + + public void PutTransactionState(UInt256 hash, TransactionLogState state) + { + var key = new KeyBuilder(Prefix_Id, Prefix_Transaction) + .Add(hash) + .ToArray(); + _snapshot.Put(key, state.ToArray()); + } + + public Guid PutStackItemState(StackItem stackItem) + { + var id = Guid.NewGuid(); + var key = new KeyBuilder(Prefix_Id, Prefix_StackItem) + .Add(id.ToByteArray()) + .ToArray(); + try + { + _snapshot.Put(key, BinarySerializer.Serialize(stackItem, ExecutionEngineLimits.Default with { MaxItemSize = (uint)Settings.Default.MaxStackSize })); + } + catch + { + _snapshot.Put(key, BinarySerializer.Serialize(StackItem.Null, ExecutionEngineLimits.Default with { MaxItemSize = (uint)Settings.Default.MaxStackSize })); + } + return id; + } + + #endregion + + #region Find + + public IEnumerable<(BlockLogState State, TriggerType Trigger)> FindBlockState(UInt256 hash) + { + var prefixKey = new KeyBuilder(Prefix_Id, Prefix_Block) + .Add(hash) + .ToArray(); + foreach (var (key, value) in _snapshot.Seek(prefixKey, SeekDirection.Forward)) + { + if (key.AsSpan().StartsWith(prefixKey)) + yield return (value.AsSerializable(), (TriggerType)key.AsSpan(Prefix_Block_Trigger_Size)[0]); + else + yield break; + } + } + + public IEnumerable FindContractState(UInt160 scriptHash, uint page, uint pageSize) + { + var prefix = new KeyBuilder(Prefix_Id, Prefix_Contract) + .Add(scriptHash) + .ToArray(); + var prefixKey = new KeyBuilder(Prefix_Id, Prefix_Contract) + .Add(scriptHash) + .AddBigEndian(ulong.MaxValue) // Get newest to oldest (timestamp) + .ToArray(); + uint index = 1; + foreach (var (key, value) in _snapshot.Seek(prefixKey, SeekDirection.Backward)) // Get newest to oldest + { + if (key.AsSpan().StartsWith(prefix)) + { + if (index >= page && index < (pageSize + page)) + yield return value.AsSerializable(); + index++; + } + else + yield break; + } + } + + public IEnumerable FindContractState(UInt160 scriptHash, TriggerType trigger, uint page, uint pageSize) + { + var prefix = new KeyBuilder(Prefix_Id, Prefix_Contract) + .Add(scriptHash) + .ToArray(); + var prefixKey = new KeyBuilder(Prefix_Id, Prefix_Contract) + .Add(scriptHash) + .AddBigEndian(ulong.MaxValue) // Get newest to oldest (timestamp) + .ToArray(); + uint index = 1; + foreach (var (key, value) in _snapshot.Seek(prefixKey, SeekDirection.Backward)) // Get newest to oldest + { + if (key.AsSpan().StartsWith(prefix)) + { + var state = value.AsSerializable(); + if (state.Trigger == trigger) + { + if (index >= page && index < (pageSize + page)) + yield return state; + index++; + } + } + else + yield break; + } + } + + public IEnumerable FindContractState(UInt160 scriptHash, TriggerType trigger, string eventName, uint page, uint pageSize) + { + var prefix = new KeyBuilder(Prefix_Id, Prefix_Contract) + .Add(scriptHash) + .ToArray(); + var prefixKey = new KeyBuilder(Prefix_Id, Prefix_Contract) + .Add(scriptHash) + .AddBigEndian(ulong.MaxValue) // Get newest to oldest (timestamp) + .ToArray(); + uint index = 1; + foreach (var (key, value) in _snapshot.Seek(prefixKey, SeekDirection.Backward)) // Get newest to oldest + { + if (key.AsSpan().StartsWith(prefix)) + { + var state = value.AsSerializable(); + if (state.Trigger == trigger && state.EventName.Equals(eventName, StringComparison.OrdinalIgnoreCase)) + { + if (index >= page && index < (pageSize + page)) + yield return state; + index++; + } + } + else + yield break; + } + } + + public IEnumerable<(Guid ExecutionStateId, TriggerType Trigger)> FindExecutionBlockState(UInt256 hash) + { + var prefixKey = new KeyBuilder(Prefix_Id, Prefix_Execution_Block) + .Add(hash) + .ToArray(); + foreach (var (key, value) in _snapshot.Seek(prefixKey, SeekDirection.Forward)) + { + if (key.AsSpan().StartsWith(prefixKey)) + yield return (new Guid(value), (TriggerType)key.AsSpan(Prefix_Execution_Block_Trigger_Size)[0]); + else + yield break; + } + } + + #endregion + + #region TryGet + + public bool TryGetEngineState(Guid engineStateId, out EngineLogState state) + { + var key = new KeyBuilder(Prefix_Id, Prefix_Engine) + .Add(engineStateId.ToByteArray()) + .ToArray(); + var data = _snapshot.TryGet(key); + state = data?.AsSerializable()!; + return data != null && data.Length > 0; + } + + public bool TryGetTransactionEngineState(UInt256 hash, out TransactionEngineLogState state) + { + var key = new KeyBuilder(Prefix_Id, Prefix_Engine_Transaction) + .Add(hash) + .ToArray(); + var data = _snapshot.TryGet(key); + state = data?.AsSerializable()!; + return data != null && data.Length > 0; + } + + public bool TryGetBlockState(UInt256 hash, TriggerType trigger, out BlockLogState state) + { + var key = new KeyBuilder(Prefix_Id, Prefix_Block) + .Add(hash) + .Add((byte)trigger) + .ToArray(); + var data = _snapshot.TryGet(key); + state = data?.AsSerializable()!; + return data != null && data.Length > 0; + } + + public bool TryGetNotifyState(Guid notifyStateId, out NotifyLogState state) + { + var key = new KeyBuilder(Prefix_Id, Prefix_Notify) + .Add(notifyStateId.ToByteArray()) + .ToArray(); + var data = _snapshot.TryGet(key); + state = data?.AsSerializable()!; + return data != null && data.Length > 0; + } + + public bool TryGetContractState(UInt160 scriptHash, ulong timestamp, uint iterIndex, out ContractLogState state) + { + var key = new KeyBuilder(Prefix_Id, Prefix_Contract) + .Add(scriptHash) + .AddBigEndian(timestamp) + .AddBigEndian(iterIndex) + .ToArray(); + var data = _snapshot.TryGet(key); + state = data?.AsSerializable()!; + return data != null && data.Length > 0; + } + + public bool TryGetExecutionState(Guid executionStateId, out ExecutionLogState state) + { + var key = new KeyBuilder(Prefix_Id, Prefix_Execution) + .Add(executionStateId.ToByteArray()) + .ToArray(); + var data = _snapshot.TryGet(key); + state = data?.AsSerializable()!; + return data != null && data.Length > 0; + } + + public bool TryGetExecutionBlockState(UInt256 blockHash, TriggerType trigger, out Guid executionStateId) + { + var key = new KeyBuilder(Prefix_Id, Prefix_Execution_Block) + .Add(blockHash) + .Add((byte)trigger) + .ToArray(); + var data = _snapshot.TryGet(key); + if (data == null) + { + executionStateId = Guid.Empty; + return false; + } + else + { + executionStateId = new Guid(data); + return true; + } + } + + public bool TryGetExecutionTransactionState(UInt256 txHash, out Guid executionStateId) + { + var key = new KeyBuilder(Prefix_Id, Prefix_Execution_Transaction) + .Add(txHash) + .ToArray(); + var data = _snapshot.TryGet(key); + if (data == null) + { + executionStateId = Guid.Empty; + return false; + } + else + { + executionStateId = new Guid(data); + return true; + } + } + + public bool TryGetTransactionState(UInt256 hash, out TransactionLogState state) + { + var key = new KeyBuilder(Prefix_Id, Prefix_Transaction) + .Add(hash) + .ToArray(); + var data = _snapshot.TryGet(key); + state = data?.AsSerializable()!; + return data != null && data.Length > 0; + } + + public bool TryGetStackItemState(Guid stackItemId, out StackItem stackItem) + { + var key = new KeyBuilder(Prefix_Id, Prefix_StackItem) + .Add(stackItemId.ToByteArray()) + .ToArray(); + var data = _snapshot.TryGet(key); + if (data == null) + { + stackItem = StackItem.Null; + return false; + } + else + { + stackItem = BinarySerializer.Deserialize(data, ExecutionEngineLimits.Default); + return true; + } + } + + #endregion + } +} diff --git a/src/Plugins/ApplicationLogs/Store/Models/ApplicationEngineLogModel.cs b/src/Plugins/ApplicationLogs/Store/Models/ApplicationEngineLogModel.cs new file mode 100644 index 0000000000..edc85b1da6 --- /dev/null +++ b/src/Plugins/ApplicationLogs/Store/Models/ApplicationEngineLogModel.cs @@ -0,0 +1,28 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ApplicationEngineLogModel.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Plugins.ApplicationLogs.Store.States; + +namespace Neo.Plugins.ApplicationLogs.Store.Models +{ + public class ApplicationEngineLogModel + { + public required UInt160 ScriptHash { get; init; } + public required string Message { get; init; } + + public static ApplicationEngineLogModel Create(EngineLogState logEventState) => + new() + { + ScriptHash = logEventState.ScriptHash, + Message = logEventState.Message, + }; + } +} diff --git a/src/Plugins/ApplicationLogs/Store/Models/BlockchainEventModel.cs b/src/Plugins/ApplicationLogs/Store/Models/BlockchainEventModel.cs new file mode 100644 index 0000000000..b067d02f9d --- /dev/null +++ b/src/Plugins/ApplicationLogs/Store/Models/BlockchainEventModel.cs @@ -0,0 +1,47 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// BlockchainEventModel.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Plugins.ApplicationLogs.Store.States; +using Neo.VM.Types; + +namespace Neo.Plugins.ApplicationLogs.Store.Models +{ + public class BlockchainEventModel + { + public required UInt160 ScriptHash { get; init; } + public required string EventName { get; init; } + public required StackItem[] State { get; init; } + + public static BlockchainEventModel Create(UInt160 scriptHash, string eventName, params StackItem[] state) => + new() + { + ScriptHash = scriptHash, + EventName = eventName ?? string.Empty, + State = state, + }; + + public static BlockchainEventModel Create(NotifyLogState notifyLogState, params StackItem[] state) => + new() + { + ScriptHash = notifyLogState.ScriptHash, + EventName = notifyLogState.EventName, + State = state, + }; + + public static BlockchainEventModel Create(ContractLogState contractLogState, params StackItem[] state) => + new() + { + ScriptHash = contractLogState.ScriptHash, + EventName = contractLogState.EventName, + State = state, + }; + } +} diff --git a/src/Plugins/ApplicationLogs/Store/Models/BlockchainExecutionModel.cs b/src/Plugins/ApplicationLogs/Store/Models/BlockchainExecutionModel.cs new file mode 100644 index 0000000000..e8ee0fc180 --- /dev/null +++ b/src/Plugins/ApplicationLogs/Store/Models/BlockchainExecutionModel.cs @@ -0,0 +1,41 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// BlockchainExecutionModel.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Plugins.ApplicationLogs.Store.States; +using Neo.SmartContract; +using Neo.VM; +using Neo.VM.Types; + +namespace Neo.Plugins.ApplicationLogs.Store.Models +{ + public class BlockchainExecutionModel + { + public required TriggerType Trigger { get; init; } + public required VMState VmState { get; init; } + public required string Exception { get; init; } + public required long GasConsumed { get; init; } + public required StackItem[] Stack { get; init; } + public required BlockchainEventModel[] Notifications { get; set; } + public required ApplicationEngineLogModel[] Logs { get; set; } + + public static BlockchainExecutionModel Create(TriggerType trigger, ExecutionLogState executionLogState, params StackItem[] stack) => + new() + { + Trigger = trigger, + VmState = executionLogState.VmState, + Exception = executionLogState.Exception ?? string.Empty, + GasConsumed = executionLogState.GasConsumed, + Stack = stack, + Notifications = [], + Logs = [] + }; + } +} diff --git a/src/Plugins/ApplicationLogs/Store/NeoStore.cs b/src/Plugins/ApplicationLogs/Store/NeoStore.cs new file mode 100644 index 0000000000..bc61a9b03c --- /dev/null +++ b/src/Plugins/ApplicationLogs/Store/NeoStore.cs @@ -0,0 +1,305 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// NeoStore.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.Plugins.ApplicationLogs.Store.Models; +using Neo.Plugins.ApplicationLogs.Store.States; +using Neo.SmartContract; +using Neo.VM.Types; + +namespace Neo.Plugins.ApplicationLogs.Store +{ + public sealed class NeoStore : IDisposable + { + #region Globals + + private readonly IStore _store; + private ISnapshot _blocklogsnapshot; + + #endregion + + #region ctor + + public NeoStore( + IStore store) + { + _store = store; + } + + #endregion + + #region IDisposable + + public void Dispose() + { + _store?.Dispose(); + GC.SuppressFinalize(this); + } + + #endregion + + #region Batching + + public void StartBlockLogBatch() + { + _blocklogsnapshot?.Dispose(); + _blocklogsnapshot = _store.GetSnapshot(); + } + + public void CommitBlockLog() => + _blocklogsnapshot?.Commit(); + + #endregion + + #region Store + + public IStore GetStore() => _store; + + #endregion + + #region Contract + + public IReadOnlyCollection<(BlockchainEventModel NotifyLog, UInt256 TxHash)> GetContractLog(UInt160 scriptHash, uint page = 1, uint pageSize = 10) + { + using var lss = new LogStorageStore(_store.GetSnapshot()); + var lstModels = new List<(BlockchainEventModel NotifyLog, UInt256 TxHash)>(); + foreach (var contractState in lss.FindContractState(scriptHash, page, pageSize)) + lstModels.Add((BlockchainEventModel.Create(contractState, CreateStackItemArray(lss, contractState.StackItemIds)), contractState.TransactionHash)); + return lstModels; + } + + public IReadOnlyCollection<(BlockchainEventModel NotifyLog, UInt256 TxHash)> GetContractLog(UInt160 scriptHash, TriggerType triggerType, uint page = 1, uint pageSize = 10) + { + using var lss = new LogStorageStore(_store.GetSnapshot()); + var lstModels = new List<(BlockchainEventModel NotifyLog, UInt256 TxHash)>(); + foreach (var contractState in lss.FindContractState(scriptHash, triggerType, page, pageSize)) + lstModels.Add((BlockchainEventModel.Create(contractState, CreateStackItemArray(lss, contractState.StackItemIds)), contractState.TransactionHash)); + return lstModels; + } + + public IReadOnlyCollection<(BlockchainEventModel NotifyLog, UInt256 TxHash)> GetContractLog(UInt160 scriptHash, TriggerType triggerType, string eventName, uint page = 1, uint pageSize = 10) + { + using var lss = new LogStorageStore(_store.GetSnapshot()); + var lstModels = new List<(BlockchainEventModel NotifyLog, UInt256 TxHash)>(); + foreach (var contractState in lss.FindContractState(scriptHash, triggerType, eventName, page, pageSize)) + lstModels.Add((BlockchainEventModel.Create(contractState, CreateStackItemArray(lss, contractState.StackItemIds)), contractState.TransactionHash)); + return lstModels; + } + + #endregion + + #region Engine + + public void PutTransactionEngineLogState(UInt256 hash, IReadOnlyList logs) + { + using var lss = new LogStorageStore(_blocklogsnapshot); + var ids = new List(); + foreach (var log in logs) + ids.Add(lss.PutEngineState(EngineLogState.Create(log.ScriptHash, log.Message))); + lss.PutTransactionEngineState(hash, TransactionEngineLogState.Create(ids.ToArray())); + } + + #endregion + + #region Block + + public BlockchainExecutionModel GetBlockLog(UInt256 hash, TriggerType trigger) + { + using var lss = new LogStorageStore(_store.GetSnapshot()); + if (lss.TryGetExecutionBlockState(hash, trigger, out var executionBlockStateId) && + lss.TryGetExecutionState(executionBlockStateId, out var executionLogState)) + { + var model = BlockchainExecutionModel.Create(trigger, executionLogState, CreateStackItemArray(lss, executionLogState.StackItemIds)); + if (lss.TryGetBlockState(hash, trigger, out var blockLogState)) + { + var lstOfEventModel = new List(); + foreach (var notifyLogItem in blockLogState.NotifyLogIds) + { + if (lss.TryGetNotifyState(notifyLogItem, out var notifyLogState)) + lstOfEventModel.Add(BlockchainEventModel.Create(notifyLogState, CreateStackItemArray(lss, notifyLogState.StackItemIds))); + } + model.Notifications = lstOfEventModel.ToArray(); + } + return model; + } + return null; + } + + public BlockchainExecutionModel GetBlockLog(UInt256 hash, TriggerType trigger, string eventName) + { + using var lss = new LogStorageStore(_store.GetSnapshot()); + if (lss.TryGetExecutionBlockState(hash, trigger, out var executionBlockStateId) && + lss.TryGetExecutionState(executionBlockStateId, out var executionLogState)) + { + var model = BlockchainExecutionModel.Create(trigger, executionLogState, CreateStackItemArray(lss, executionLogState.StackItemIds)); + if (lss.TryGetBlockState(hash, trigger, out var blockLogState)) + { + var lstOfEventModel = new List(); + foreach (var notifyLogItem in blockLogState.NotifyLogIds) + { + if (lss.TryGetNotifyState(notifyLogItem, out var notifyLogState)) + { + if (notifyLogState.EventName.Equals(eventName, StringComparison.OrdinalIgnoreCase)) + lstOfEventModel.Add(BlockchainEventModel.Create(notifyLogState, CreateStackItemArray(lss, notifyLogState.StackItemIds))); + } + } + model.Notifications = lstOfEventModel.ToArray(); + } + return model; + } + return null; + } + + public void PutBlockLog(Block block, IReadOnlyList applicationExecutedList) + { + foreach (var appExecution in applicationExecutedList) + { + using var lss = new LogStorageStore(_blocklogsnapshot); + var exeStateId = PutExecutionLogBlock(lss, block, appExecution); + PutBlockAndTransactionLog(lss, block, appExecution, exeStateId); + } + } + + private static Guid PutExecutionLogBlock(LogStorageStore logStore, Block block, Blockchain.ApplicationExecuted appExecution) + { + var exeStateId = logStore.PutExecutionState(ExecutionLogState.Create(appExecution, CreateStackItemIdList(logStore, appExecution))); + logStore.PutExecutionBlockState(block.Hash, appExecution.Trigger, exeStateId); + return exeStateId; + } + + #endregion + + #region Transaction + + public BlockchainExecutionModel GetTransactionLog(UInt256 hash) + { + using var lss = new LogStorageStore(_store.GetSnapshot()); + if (lss.TryGetExecutionTransactionState(hash, out var executionTransactionStateId) && + lss.TryGetExecutionState(executionTransactionStateId, out var executionLogState)) + { + var model = BlockchainExecutionModel.Create(TriggerType.Application, executionLogState, CreateStackItemArray(lss, executionLogState.StackItemIds)); + if (lss.TryGetTransactionState(hash, out var transactionLogState)) + { + var lstOfEventModel = new List(); + foreach (var notifyLogItem in transactionLogState.NotifyLogIds) + { + if (lss.TryGetNotifyState(notifyLogItem, out var notifyLogState)) + lstOfEventModel.Add(BlockchainEventModel.Create(notifyLogState, CreateStackItemArray(lss, notifyLogState.StackItemIds))); + } + model.Notifications = lstOfEventModel.ToArray(); + + if (lss.TryGetTransactionEngineState(hash, out var transactionEngineLogState)) + { + var lstOfLogs = new List(); + foreach (var logItem in transactionEngineLogState.LogIds) + { + if (lss.TryGetEngineState(logItem, out var engineLogState)) + lstOfLogs.Add(ApplicationEngineLogModel.Create(engineLogState)); + } + model.Logs = lstOfLogs.ToArray(); + } + } + return model; + } + return null; + } + + public BlockchainExecutionModel GetTransactionLog(UInt256 hash, string eventName) + { + using var lss = new LogStorageStore(_store.GetSnapshot()); + if (lss.TryGetExecutionTransactionState(hash, out var executionTransactionStateId) && + lss.TryGetExecutionState(executionTransactionStateId, out var executionLogState)) + { + var model = BlockchainExecutionModel.Create(TriggerType.Application, executionLogState, CreateStackItemArray(lss, executionLogState.StackItemIds)); + if (lss.TryGetTransactionState(hash, out var transactionLogState)) + { + var lstOfEventModel = new List(); + foreach (var notifyLogItem in transactionLogState.NotifyLogIds) + { + if (lss.TryGetNotifyState(notifyLogItem, out var notifyLogState)) + { + if (notifyLogState.EventName.Equals(eventName, StringComparison.OrdinalIgnoreCase)) + lstOfEventModel.Add(BlockchainEventModel.Create(notifyLogState, CreateStackItemArray(lss, notifyLogState.StackItemIds))); + } + } + model.Notifications = lstOfEventModel.ToArray(); + + if (lss.TryGetTransactionEngineState(hash, out var transactionEngineLogState)) + { + var lstOfLogs = new List(); + foreach (var logItem in transactionEngineLogState.LogIds) + { + if (lss.TryGetEngineState(logItem, out var engineLogState)) + lstOfLogs.Add(ApplicationEngineLogModel.Create(engineLogState)); + } + model.Logs = lstOfLogs.ToArray(); + } + } + return model; + } + return null; + } + + private static void PutBlockAndTransactionLog(LogStorageStore logStore, Block block, Blockchain.ApplicationExecuted appExecution, Guid executionStateId) + { + if (appExecution.Transaction != null) + logStore.PutExecutionTransactionState(appExecution.Transaction.Hash, executionStateId); // For looking up execution log by transaction hash + + var lstNotifyLogIds = new List(); + for (uint i = 0; i < appExecution.Notifications.Length; i++) + { + var notifyItem = appExecution.Notifications[i]; + var stackItemStateIds = CreateStackItemIdList(logStore, notifyItem); // Save notify stack items + logStore.PutContractState(notifyItem.ScriptHash, block.Timestamp, i, // save notifylog for the contracts + ContractLogState.Create(appExecution, notifyItem, stackItemStateIds)); + lstNotifyLogIds.Add(logStore.PutNotifyState(NotifyLogState.Create(notifyItem, stackItemStateIds))); + } + + if (appExecution.Transaction != null) + logStore.PutTransactionState(appExecution.Transaction.Hash, TransactionLogState.Create(lstNotifyLogIds.ToArray())); + + logStore.PutBlockState(block.Hash, appExecution.Trigger, BlockLogState.Create(lstNotifyLogIds.ToArray())); + } + + #endregion + + #region StackItem + + private static StackItem[] CreateStackItemArray(LogStorageStore logStore, Guid[] stackItemIds) + { + var lstStackItems = new List(); + foreach (var stackItemId in stackItemIds) + if (logStore.TryGetStackItemState(stackItemId, out var stackItem)) + lstStackItems.Add(stackItem); + return lstStackItems.ToArray(); + } + + private static Guid[] CreateStackItemIdList(LogStorageStore logStore, Blockchain.ApplicationExecuted appExecution) + { + var lstStackItemIds = new List(); + foreach (var stackItem in appExecution.Stack) + lstStackItemIds.Add(logStore.PutStackItemState(stackItem)); + return lstStackItemIds.ToArray(); + } + + private static Guid[] CreateStackItemIdList(LogStorageStore logStore, NotifyEventArgs notifyEventArgs) + { + var lstStackItemIds = new List(); + foreach (var stackItem in notifyEventArgs.State) + lstStackItemIds.Add(logStore.PutStackItemState(stackItem)); + return lstStackItemIds.ToArray(); + } + + #endregion + } +} diff --git a/src/Plugins/ApplicationLogs/Store/States/BlockLogState.cs b/src/Plugins/ApplicationLogs/Store/States/BlockLogState.cs new file mode 100644 index 0000000000..54369a672a --- /dev/null +++ b/src/Plugins/ApplicationLogs/Store/States/BlockLogState.cs @@ -0,0 +1,72 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// BlockLogState.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo; +using Neo.IO; + +namespace Neo.Plugins.ApplicationLogs.Store.States +{ + public class BlockLogState : ISerializable, IEquatable + { + public Guid[] NotifyLogIds { get; private set; } = []; + + public static BlockLogState Create(Guid[] notifyLogIds) => + new() + { + NotifyLogIds = notifyLogIds, + }; + + #region ISerializable + + public virtual int Size => + sizeof(uint) + + NotifyLogIds.Sum(s => s.ToByteArray().GetVarSize()); + + public virtual void Deserialize(ref MemoryReader reader) + { + // It should be safe because it filled from a block's notifications. + uint aLen = reader.ReadUInt32(); + NotifyLogIds = new Guid[aLen]; + for (int i = 0; i < aLen; i++) + NotifyLogIds[i] = new Guid(reader.ReadVarMemory().Span); + } + + public virtual void Serialize(BinaryWriter writer) + { + writer.Write((uint)NotifyLogIds.Length); + for (int i = 0; i < NotifyLogIds.Length; i++) + writer.WriteVarBytes(NotifyLogIds[i].ToByteArray()); + } + + #endregion + + #region IEquatable + + public bool Equals(BlockLogState other) => + NotifyLogIds.SequenceEqual(other.NotifyLogIds); + + public override bool Equals(object obj) + { + if (ReferenceEquals(this, obj)) return true; + return Equals(obj as BlockLogState); + } + + public override int GetHashCode() + { + var h = new HashCode(); + foreach (var id in NotifyLogIds) + h.Add(id); + return h.ToHashCode(); + } + + #endregion + } +} diff --git a/src/Plugins/ApplicationLogs/Store/States/ContractLogState.cs b/src/Plugins/ApplicationLogs/Store/States/ContractLogState.cs new file mode 100644 index 0000000000..011886f67c --- /dev/null +++ b/src/Plugins/ApplicationLogs/Store/States/ContractLogState.cs @@ -0,0 +1,74 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractLogState.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo; +using Neo.IO; +using Neo.Ledger; +using Neo.SmartContract; + +namespace Neo.Plugins.ApplicationLogs.Store.States +{ + public class ContractLogState : NotifyLogState, IEquatable + { + public UInt256 TransactionHash { get; private set; } = new(); + public TriggerType Trigger { get; private set; } = TriggerType.All; + + public static ContractLogState Create(Blockchain.ApplicationExecuted applicationExecuted, NotifyEventArgs notifyEventArgs, Guid[] stackItemIds) => + new() + { + TransactionHash = applicationExecuted.Transaction?.Hash ?? new(), + ScriptHash = notifyEventArgs.ScriptHash, + Trigger = applicationExecuted.Trigger, + EventName = notifyEventArgs.EventName, + StackItemIds = stackItemIds, + }; + + #region ISerializable + + public override int Size => + TransactionHash.Size + + sizeof(byte) + + base.Size; + + public override void Deserialize(ref MemoryReader reader) + { + TransactionHash.Deserialize(ref reader); + Trigger = (TriggerType)reader.ReadByte(); + base.Deserialize(ref reader); + } + + public override void Serialize(BinaryWriter writer) + { + TransactionHash.Serialize(writer); + writer.Write((byte)Trigger); + base.Serialize(writer); + } + + #endregion + + #region IEquatable + + public bool Equals(ContractLogState other) => + Trigger == other.Trigger && EventName == other.EventName && + TransactionHash == other.TransactionHash && StackItemIds.SequenceEqual(other.StackItemIds); + + public override bool Equals(object obj) + { + if (ReferenceEquals(this, obj)) return true; + return Equals(obj as ContractLogState); + } + + public override int GetHashCode() => + HashCode.Combine(TransactionHash, Trigger, base.GetHashCode()); + + #endregion + } +} diff --git a/src/Plugins/ApplicationLogs/Store/States/EngineLogState.cs b/src/Plugins/ApplicationLogs/Store/States/EngineLogState.cs new file mode 100644 index 0000000000..96f9041aaf --- /dev/null +++ b/src/Plugins/ApplicationLogs/Store/States/EngineLogState.cs @@ -0,0 +1,66 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// EngineLogState.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; + +namespace Neo.Plugins.ApplicationLogs.Store.States +{ + public class EngineLogState : ISerializable, IEquatable + { + public UInt160 ScriptHash { get; private set; } = new(); + public string Message { get; private set; } = string.Empty; + + public static EngineLogState Create(UInt160 scriptHash, string message) => + new() + { + ScriptHash = scriptHash, + Message = message, + }; + + #region ISerializable + + public virtual int Size => + ScriptHash.Size + + Message.GetVarSize(); + + public virtual void Deserialize(ref MemoryReader reader) + { + ScriptHash.Deserialize(ref reader); + // It should be safe because it filled from a transaction's logs. + Message = reader.ReadVarString(); + } + + public virtual void Serialize(BinaryWriter writer) + { + ScriptHash.Serialize(writer); + writer.WriteVarString(Message ?? string.Empty); + } + + #endregion + + #region IEquatable + + public bool Equals(EngineLogState other) => + ScriptHash == other.ScriptHash && + Message == other.Message; + + public override bool Equals(object obj) + { + if (ReferenceEquals(this, obj)) return true; + return Equals(obj as EngineLogState); + } + + public override int GetHashCode() => + HashCode.Combine(ScriptHash, Message); + + #endregion + } +} diff --git a/src/Plugins/ApplicationLogs/Store/States/ExecutionLogState.cs b/src/Plugins/ApplicationLogs/Store/States/ExecutionLogState.cs new file mode 100644 index 0000000000..210ac36283 --- /dev/null +++ b/src/Plugins/ApplicationLogs/Store/States/ExecutionLogState.cs @@ -0,0 +1,94 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ExecutionLogState.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using Neo.Ledger; +using Neo.VM; + +namespace Neo.Plugins.ApplicationLogs.Store.States +{ + public class ExecutionLogState : ISerializable, IEquatable + { + public VMState VmState { get; private set; } = VMState.NONE; + public string Exception { get; private set; } = string.Empty; + public long GasConsumed { get; private set; } = 0L; + public Guid[] StackItemIds { get; private set; } = []; + + public static ExecutionLogState Create(Blockchain.ApplicationExecuted appExecution, Guid[] stackItemIds) => + new() + { + VmState = appExecution.VMState, + Exception = appExecution.Exception?.InnerException?.Message ?? appExecution.Exception?.Message!, + GasConsumed = appExecution.GasConsumed, + StackItemIds = stackItemIds, + }; + + #region ISerializable + + public int Size => + sizeof(byte) + + Exception.GetVarSize() + + sizeof(long) + + sizeof(uint) + + StackItemIds.Sum(s => s.ToByteArray().GetVarSize()); + + public void Deserialize(ref MemoryReader reader) + { + VmState = (VMState)reader.ReadByte(); + Exception = reader.ReadVarString(); + GasConsumed = reader.ReadInt64(); + + // It should be safe because it filled from a transaction's stack. + uint aLen = reader.ReadUInt32(); + StackItemIds = new Guid[aLen]; + for (int i = 0; i < aLen; i++) + StackItemIds[i] = new Guid(reader.ReadVarMemory().Span); + } + + public void Serialize(BinaryWriter writer) + { + writer.Write((byte)VmState); + writer.WriteVarString(Exception ?? string.Empty); + writer.Write(GasConsumed); + + writer.Write((uint)StackItemIds.Length); + for (int i = 0; i < StackItemIds.Length; i++) + writer.WriteVarBytes(StackItemIds[i].ToByteArray()); + } + + #endregion + + #region IEquatable + + public bool Equals(ExecutionLogState other) => + VmState == other.VmState && Exception == other.Exception && + GasConsumed == other.GasConsumed && StackItemIds.SequenceEqual(other.StackItemIds); + + public override bool Equals(object obj) + { + if (ReferenceEquals(this, obj)) return true; + return Equals(obj as ExecutionLogState); + } + + public override int GetHashCode() + { + var h = new HashCode(); + h.Add(VmState); + h.Add(Exception); + h.Add(GasConsumed); + foreach (var id in StackItemIds) + h.Add(id); + return h.ToHashCode(); + } + + #endregion + } +} diff --git a/src/Plugins/ApplicationLogs/Store/States/NotifyLogState.cs b/src/Plugins/ApplicationLogs/Store/States/NotifyLogState.cs new file mode 100644 index 0000000000..70b53268e5 --- /dev/null +++ b/src/Plugins/ApplicationLogs/Store/States/NotifyLogState.cs @@ -0,0 +1,87 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// NotifyLogState.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo; +using Neo.IO; +using Neo.SmartContract; + +namespace Neo.Plugins.ApplicationLogs.Store.States +{ + public class NotifyLogState : ISerializable, IEquatable + { + public UInt160 ScriptHash { get; protected set; } = new(); + public string EventName { get; protected set; } = string.Empty; + public Guid[] StackItemIds { get; protected set; } = []; + + public static NotifyLogState Create(NotifyEventArgs notifyItem, Guid[] stackItemsIds) => + new() + { + ScriptHash = notifyItem.ScriptHash, + EventName = notifyItem.EventName, + StackItemIds = stackItemsIds, + }; + + #region ISerializable + + public virtual int Size => + ScriptHash.Size + + EventName.GetVarSize() + + StackItemIds.Sum(s => s.ToByteArray().GetVarSize()); + + public virtual void Deserialize(ref MemoryReader reader) + { + ScriptHash.Deserialize(ref reader); + EventName = reader.ReadVarString(); + + // It should be safe because it filled from a transaction's notifications. + uint aLen = reader.ReadUInt32(); + StackItemIds = new Guid[aLen]; + for (var i = 0; i < aLen; i++) + StackItemIds[i] = new Guid(reader.ReadVarMemory().Span); + } + + public virtual void Serialize(BinaryWriter writer) + { + ScriptHash.Serialize(writer); + writer.WriteVarString(EventName ?? string.Empty); + + writer.Write((uint)StackItemIds.Length); + for (var i = 0; i < StackItemIds.Length; i++) + writer.WriteVarBytes(StackItemIds[i].ToByteArray()); + } + + #endregion + + #region IEquatable + + public bool Equals(NotifyLogState other) => + EventName == other.EventName && ScriptHash == other.ScriptHash && + StackItemIds.SequenceEqual(other.StackItemIds); + + public override bool Equals(object obj) + { + if (ReferenceEquals(this, obj)) return true; + return Equals(obj as NotifyLogState); + } + + public override int GetHashCode() + { + var h = new HashCode(); + h.Add(ScriptHash); + h.Add(EventName); + foreach (var id in StackItemIds) + h.Add(id); + return h.ToHashCode(); + } + + #endregion + } +} diff --git a/src/Plugins/ApplicationLogs/Store/States/TransactionEngineLogState.cs b/src/Plugins/ApplicationLogs/Store/States/TransactionEngineLogState.cs new file mode 100644 index 0000000000..9417f984e2 --- /dev/null +++ b/src/Plugins/ApplicationLogs/Store/States/TransactionEngineLogState.cs @@ -0,0 +1,71 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TransactionEngineLogState.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; + +namespace Neo.Plugins.ApplicationLogs.Store.States +{ + public class TransactionEngineLogState : ISerializable, IEquatable + { + public Guid[] LogIds { get; private set; } = Array.Empty(); + + public static TransactionEngineLogState Create(Guid[] logIds) => + new() + { + LogIds = logIds, + }; + + #region ISerializable + + public virtual int Size => + sizeof(uint) + + LogIds.Sum(s => s.ToByteArray().GetVarSize()); + + public virtual void Deserialize(ref MemoryReader reader) + { + // It should be safe because it filled from a transaction's logs. + uint aLen = reader.ReadUInt32(); + LogIds = new Guid[aLen]; + for (int i = 0; i < aLen; i++) + LogIds[i] = new Guid(reader.ReadVarMemory().Span); + } + + public virtual void Serialize(BinaryWriter writer) + { + writer.Write((uint)LogIds.Length); + for (int i = 0; i < LogIds.Length; i++) + writer.WriteVarBytes(LogIds[i].ToByteArray()); + } + + #endregion + + #region IEquatable + + public bool Equals(TransactionEngineLogState other) => + LogIds.SequenceEqual(other.LogIds); + + public override bool Equals(object obj) + { + if (ReferenceEquals(this, obj)) return true; + return Equals(obj as TransactionEngineLogState); + } + + public override int GetHashCode() + { + var h = new HashCode(); + foreach (var id in LogIds) + h.Add(id); + return h.ToHashCode(); + } + + #endregion + } +} diff --git a/src/Plugins/ApplicationLogs/Store/States/TransactionLogState.cs b/src/Plugins/ApplicationLogs/Store/States/TransactionLogState.cs new file mode 100644 index 0000000000..1667478509 --- /dev/null +++ b/src/Plugins/ApplicationLogs/Store/States/TransactionLogState.cs @@ -0,0 +1,71 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TransactionLogState.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; + +namespace Neo.Plugins.ApplicationLogs.Store.States +{ + public class TransactionLogState : ISerializable, IEquatable + { + public Guid[] NotifyLogIds { get; private set; } = Array.Empty(); + + public static TransactionLogState Create(Guid[] notifyLogIds) => + new() + { + NotifyLogIds = notifyLogIds, + }; + + #region ISerializable + + public virtual int Size => + sizeof(uint) + + NotifyLogIds.Sum(s => s.ToByteArray().GetVarSize()); + + public virtual void Deserialize(ref MemoryReader reader) + { + // It should be safe because it filled from a transaction's notifications. + uint aLen = reader.ReadUInt32(); + NotifyLogIds = new Guid[aLen]; + for (int i = 0; i < aLen; i++) + NotifyLogIds[i] = new Guid(reader.ReadVarMemory().Span); + } + + public virtual void Serialize(BinaryWriter writer) + { + writer.Write((uint)NotifyLogIds.Length); + for (int i = 0; i < NotifyLogIds.Length; i++) + writer.WriteVarBytes(NotifyLogIds[i].ToByteArray()); + } + + #endregion + + #region IEquatable + + public bool Equals(TransactionLogState other) => + NotifyLogIds.SequenceEqual(other.NotifyLogIds); + + public override bool Equals(object obj) + { + if (ReferenceEquals(this, obj)) return true; + return Equals(obj as TransactionLogState); + } + + public override int GetHashCode() + { + var h = new HashCode(); + foreach (var id in NotifyLogIds) + h.Add(id); + return h.ToHashCode(); + } + + #endregion + } +} diff --git a/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.Get.cs b/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.Get.cs new file mode 100644 index 0000000000..edffc1cb09 --- /dev/null +++ b/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.Get.cs @@ -0,0 +1,116 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ConsensusContext.Get.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Network.P2P.Payloads; +using Neo.Plugins.DBFTPlugin.Messages; +using Neo.SmartContract; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Neo.Plugins.DBFTPlugin.Consensus +{ + partial class ConsensusContext + { + public ConsensusMessage GetMessage(ExtensiblePayload payload) + { + if (payload is null) return null; + if (!cachedMessages.TryGetValue(payload.Hash, out ConsensusMessage message)) + cachedMessages.Add(payload.Hash, message = ConsensusMessage.DeserializeFrom(payload.Data)); + return message; + } + + public T GetMessage(ExtensiblePayload payload) where T : ConsensusMessage + { + return (T)GetMessage(payload); + } + + private RecoveryMessage.ChangeViewPayloadCompact GetChangeViewPayloadCompact(ExtensiblePayload payload) + { + ChangeView message = GetMessage(payload); + return new RecoveryMessage.ChangeViewPayloadCompact + { + ValidatorIndex = message.ValidatorIndex, + OriginalViewNumber = message.ViewNumber, + Timestamp = message.Timestamp, + InvocationScript = payload.Witness.InvocationScript + }; + } + + private RecoveryMessage.CommitPayloadCompact GetCommitPayloadCompact(ExtensiblePayload payload) + { + Commit message = GetMessage(payload); + return new RecoveryMessage.CommitPayloadCompact + { + ViewNumber = message.ViewNumber, + ValidatorIndex = message.ValidatorIndex, + Signature = message.Signature, + InvocationScript = payload.Witness.InvocationScript + }; + } + + private RecoveryMessage.PreparationPayloadCompact GetPreparationPayloadCompact(ExtensiblePayload payload) + { + return new RecoveryMessage.PreparationPayloadCompact + { + ValidatorIndex = GetMessage(payload).ValidatorIndex, + InvocationScript = payload.Witness.InvocationScript + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte GetPrimaryIndex(byte viewNumber) + { + int p = ((int)Block.Index - viewNumber) % Validators.Length; + return p >= 0 ? (byte)p : (byte)(p + Validators.Length); + } + + public UInt160 GetSender(int index) + { + return Contract.CreateSignatureRedeemScript(Validators[index]).ToScriptHash(); + } + + /// + /// Return the expected block size + /// + public int GetExpectedBlockSize() + { + return GetExpectedBlockSizeWithoutTransactions(Transactions.Count) + // Base size + Transactions.Values.Sum(u => u.Size); // Sum Txs + } + + /// + /// Return the expected block system fee + /// + public long GetExpectedBlockSystemFee() + { + return Transactions.Values.Sum(u => u.SystemFee); // Sum Txs + } + + /// + /// Return the expected block size without txs + /// + /// Expected transactions + internal int GetExpectedBlockSizeWithoutTransactions(int expectedTransactions) + { + return + sizeof(uint) + // Version + UInt256.Length + // PrevHash + UInt256.Length + // MerkleRoot + sizeof(ulong) + // Timestamp + sizeof(ulong) + // Nonce + sizeof(uint) + // Index + sizeof(byte) + // PrimaryIndex + UInt160.Length + // NextConsensus + 1 + _witnessSize + // Witness + IO.Helper.GetVarSize(expectedTransactions); + } + } +} diff --git a/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.MakePayload.cs b/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.MakePayload.cs new file mode 100644 index 0000000000..ee3b8a7747 --- /dev/null +++ b/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.MakePayload.cs @@ -0,0 +1,177 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ConsensusContext.MakePayload.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Plugins.DBFTPlugin.Messages; +using Neo.Plugins.DBFTPlugin.Types; +using Neo.SmartContract; +using Neo.Wallets; +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Linq; + +namespace Neo.Plugins.DBFTPlugin.Consensus +{ + partial class ConsensusContext + { + public ExtensiblePayload MakeChangeView(ChangeViewReason reason) + { + return ChangeViewPayloads[MyIndex] = MakeSignedPayload(new ChangeView + { + Reason = reason, + Timestamp = TimeProvider.Current.UtcNow.ToTimestampMS() + }); + } + + public ExtensiblePayload MakeCommit() + { + return CommitPayloads[MyIndex] ?? (CommitPayloads[MyIndex] = MakeSignedPayload(new Commit + { + Signature = EnsureHeader().Sign(keyPair, neoSystem.Settings.Network) + })); + } + + private ExtensiblePayload MakeSignedPayload(ConsensusMessage message) + { + message.BlockIndex = Block.Index; + message.ValidatorIndex = (byte)MyIndex; + message.ViewNumber = ViewNumber; + ExtensiblePayload payload = CreatePayload(message, null); + SignPayload(payload); + return payload; + } + + private void SignPayload(ExtensiblePayload payload) + { + ContractParametersContext sc; + try + { + sc = new ContractParametersContext(neoSystem.StoreView, payload, dbftSettings.Network); + wallet.Sign(sc); + } + catch (InvalidOperationException exception) + { + Utility.Log(nameof(ConsensusContext), LogLevel.Debug, exception.ToString()); + return; + } + payload.Witness = sc.GetWitnesses()[0]; + } + + /// + /// Prevent that block exceed the max size + /// + /// Ordered transactions + internal void EnsureMaxBlockLimitation(IEnumerable txs) + { + uint maxTransactionsPerBlock = neoSystem.Settings.MaxTransactionsPerBlock; + + // Limit Speaker proposal to the limit `MaxTransactionsPerBlock` or all available transactions of the mempool + txs = txs.Take((int)maxTransactionsPerBlock); + + List hashes = new List(); + Transactions = new Dictionary(); + VerificationContext = new TransactionVerificationContext(); + + // Expected block size + var blockSize = GetExpectedBlockSizeWithoutTransactions(txs.Count()); + var blockSystemFee = 0L; + + // Iterate transaction until reach the size or maximum system fee + foreach (Transaction tx in txs) + { + // Check if maximum block size has been already exceeded with the current selected set + blockSize += tx.Size; + if (blockSize > dbftSettings.MaxBlockSize) break; + + // Check if maximum block system fee has been already exceeded with the current selected set + blockSystemFee += tx.SystemFee; + if (blockSystemFee > dbftSettings.MaxBlockSystemFee) break; + + hashes.Add(tx.Hash); + Transactions.Add(tx.Hash, tx); + VerificationContext.AddTransaction(tx); + } + + TransactionHashes = hashes.ToArray(); + } + + public ExtensiblePayload MakePrepareRequest() + { + EnsureMaxBlockLimitation(neoSystem.MemPool.GetSortedVerifiedTransactions()); + Block.Header.Timestamp = Math.Max(TimeProvider.Current.UtcNow.ToTimestampMS(), PrevHeader.Timestamp + 1); + Block.Header.Nonce = GetNonce(); + return PreparationPayloads[MyIndex] = MakeSignedPayload(new PrepareRequest + { + Version = Block.Version, + PrevHash = Block.PrevHash, + Timestamp = Block.Timestamp, + Nonce = Block.Nonce, + TransactionHashes = TransactionHashes + }); + } + + public ExtensiblePayload MakeRecoveryRequest() + { + return MakeSignedPayload(new RecoveryRequest + { + Timestamp = TimeProvider.Current.UtcNow.ToTimestampMS() + }); + } + + public ExtensiblePayload MakeRecoveryMessage() + { + PrepareRequest prepareRequestMessage = null; + if (TransactionHashes != null) + { + prepareRequestMessage = new PrepareRequest + { + Version = Block.Version, + PrevHash = Block.PrevHash, + ViewNumber = ViewNumber, + Timestamp = Block.Timestamp, + Nonce = Block.Nonce, + BlockIndex = Block.Index, + ValidatorIndex = Block.PrimaryIndex, + TransactionHashes = TransactionHashes + }; + } + return MakeSignedPayload(new RecoveryMessage + { + ChangeViewMessages = LastChangeViewPayloads.Where(p => p != null).Select(p => GetChangeViewPayloadCompact(p)).Take(M).ToDictionary(p => p.ValidatorIndex), + PrepareRequestMessage = prepareRequestMessage, + // We only need a PreparationHash set if we don't have the PrepareRequest information. + PreparationHash = TransactionHashes == null ? PreparationPayloads.Where(p => p != null).GroupBy(p => GetMessage(p).PreparationHash, (k, g) => new { Hash = k, Count = g.Count() }).OrderByDescending(p => p.Count).Select(p => p.Hash).FirstOrDefault() : null, + PreparationMessages = PreparationPayloads.Where(p => p != null).Select(p => GetPreparationPayloadCompact(p)).ToDictionary(p => p.ValidatorIndex), + CommitMessages = CommitSent + ? CommitPayloads.Where(p => p != null).Select(p => GetCommitPayloadCompact(p)).ToDictionary(p => p.ValidatorIndex) + : new Dictionary() + }); + } + + public ExtensiblePayload MakePrepareResponse() + { + return PreparationPayloads[MyIndex] = MakeSignedPayload(new PrepareResponse + { + PreparationHash = PreparationPayloads[Block.PrimaryIndex].Hash + }); + } + + private static ulong GetNonce() + { + Random _random = new(); + Span buffer = stackalloc byte[8]; + _random.NextBytes(buffer); + return BinaryPrimitives.ReadUInt64LittleEndian(buffer); + } + } +} diff --git a/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.cs b/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.cs new file mode 100644 index 0000000000..abd0309783 --- /dev/null +++ b/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.cs @@ -0,0 +1,324 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ConsensusContext.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Cryptography; +using Neo.Cryptography.ECC; +using Neo.IO; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.Plugins.DBFTPlugin.Messages; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Neo.Plugins.DBFTPlugin.Consensus +{ + public partial class ConsensusContext : IDisposable, ISerializable + { + /// + /// Key for saving consensus state. + /// + private static readonly byte[] ConsensusStateKey = { 0xf4 }; + + public Block Block; + public byte ViewNumber; + public ECPoint[] Validators; + public int MyIndex; + public UInt256[] TransactionHashes; + public Dictionary Transactions; + public ExtensiblePayload[] PreparationPayloads; + public ExtensiblePayload[] CommitPayloads; + public ExtensiblePayload[] ChangeViewPayloads; + public ExtensiblePayload[] LastChangeViewPayloads; + // LastSeenMessage array stores the height of the last seen message, for each validator. + // if this node never heard from validator i, LastSeenMessage[i] will be -1. + public Dictionary LastSeenMessage { get; private set; } + + /// + /// Store all verified unsorted transactions' senders' fee currently in the consensus context. + /// + public TransactionVerificationContext VerificationContext = new(); + + public SnapshotCache Snapshot { get; private set; } + private KeyPair keyPair; + private int _witnessSize; + private readonly NeoSystem neoSystem; + private readonly Settings dbftSettings; + private readonly Wallet wallet; + private readonly IStore store; + private Dictionary cachedMessages; + + public int F => (Validators.Length - 1) / 3; + public int M => Validators.Length - F; + public bool IsPrimary => MyIndex == Block.PrimaryIndex; + public bool IsBackup => MyIndex >= 0 && MyIndex != Block.PrimaryIndex; + public bool WatchOnly => MyIndex < 0; + public Header PrevHeader => NativeContract.Ledger.GetHeader(Snapshot, Block.PrevHash); + public int CountCommitted => CommitPayloads.Count(p => p != null); + public int CountFailed + { + get + { + if (LastSeenMessage == null) return 0; + return Validators.Count(p => !LastSeenMessage.TryGetValue(p, out var value) || value < (Block.Index - 1)); + } + } + public bool ValidatorsChanged + { + get + { + if (NativeContract.Ledger.CurrentIndex(Snapshot) == 0) return false; + UInt256 hash = NativeContract.Ledger.CurrentHash(Snapshot); + TrimmedBlock currentBlock = NativeContract.Ledger.GetTrimmedBlock(Snapshot, hash); + TrimmedBlock previousBlock = NativeContract.Ledger.GetTrimmedBlock(Snapshot, currentBlock.Header.PrevHash); + return currentBlock.Header.NextConsensus != previousBlock.Header.NextConsensus; + } + } + + #region Consensus States + public bool RequestSentOrReceived => PreparationPayloads[Block.PrimaryIndex] != null; + public bool ResponseSent => !WatchOnly && PreparationPayloads[MyIndex] != null; + public bool CommitSent => !WatchOnly && CommitPayloads[MyIndex] != null; + public bool BlockSent => Block.Transactions != null; + public bool ViewChanging => !WatchOnly && GetMessage(ChangeViewPayloads[MyIndex])?.NewViewNumber > ViewNumber; + // NotAcceptingPayloadsDueToViewChanging imposes nodes to not accept some payloads if View is Changing, + // i.e: OnTransaction function will not process any transaction; OnPrepareRequestReceived will also return; + // as well as OnPrepareResponseReceived and also similar logic for recovering. + // On the other hand, if more than MoreThanFNodesCommittedOrLost is true, we keep accepting those payloads. + // This helps the node to still commit, even while almost changing view. + public bool NotAcceptingPayloadsDueToViewChanging => ViewChanging && !MoreThanFNodesCommittedOrLost; + // A possible attack can happen if the last node to commit is malicious and either sends change view after his + // commit to stall nodes in a higher view, or if he refuses to send recovery messages. In addition, if a node + // asking change views loses network or crashes and comes back when nodes are committed in more than one higher + // numbered view, it is possible for the node accepting recovery to commit in any of the higher views, thus + // potentially splitting nodes among views and stalling the network. + public bool MoreThanFNodesCommittedOrLost => (CountCommitted + CountFailed) > F; + #endregion + + public int Size => throw new NotImplementedException(); + + public ConsensusContext(NeoSystem neoSystem, Settings settings, Wallet wallet) + { + this.wallet = wallet; + this.neoSystem = neoSystem; + dbftSettings = settings; + store = neoSystem.LoadStore(settings.RecoveryLogs); + } + + public Block CreateBlock() + { + EnsureHeader(); + Contract contract = Contract.CreateMultiSigContract(M, Validators); + ContractParametersContext sc = new ContractParametersContext(neoSystem.StoreView, Block.Header, dbftSettings.Network); + for (int i = 0, j = 0; i < Validators.Length && j < M; i++) + { + if (GetMessage(CommitPayloads[i])?.ViewNumber != ViewNumber) continue; + sc.AddSignature(contract, Validators[i], GetMessage(CommitPayloads[i]).Signature.ToArray()); + j++; + } + Block.Header.Witness = sc.GetWitnesses()[0]; + Block.Transactions = TransactionHashes.Select(p => Transactions[p]).ToArray(); + return Block; + } + + public ExtensiblePayload CreatePayload(ConsensusMessage message, ReadOnlyMemory invocationScript = default) + { + ExtensiblePayload payload = new ExtensiblePayload + { + Category = "dBFT", + ValidBlockStart = 0, + ValidBlockEnd = message.BlockIndex, + Sender = GetSender(message.ValidatorIndex), + Data = message.ToArray(), + Witness = invocationScript.IsEmpty ? null : new Witness + { + InvocationScript = invocationScript, + VerificationScript = Contract.CreateSignatureRedeemScript(Validators[message.ValidatorIndex]) + } + }; + cachedMessages.TryAdd(payload.Hash, message); + return payload; + } + + public void Dispose() + { + Snapshot?.Dispose(); + } + + public Block EnsureHeader() + { + if (TransactionHashes == null) return null; + Block.Header.MerkleRoot ??= MerkleTree.ComputeRoot(TransactionHashes); + return Block; + } + + public bool Load() + { + byte[] data = store.TryGet(ConsensusStateKey); + if (data is null || data.Length == 0) return false; + MemoryReader reader = new(data); + try + { + Deserialize(ref reader); + } + catch (InvalidOperationException) + { + return false; + } + catch (Exception exception) + { + Utility.Log(nameof(ConsensusContext), LogLevel.Debug, exception.ToString()); + return false; + } + return true; + } + + public void Reset(byte viewNumber) + { + if (viewNumber == 0) + { + Snapshot?.Dispose(); + Snapshot = neoSystem.GetSnapshot(); + uint height = NativeContract.Ledger.CurrentIndex(Snapshot); + Block = new Block + { + Header = new Header + { + PrevHash = NativeContract.Ledger.CurrentHash(Snapshot), + Index = height + 1, + NextConsensus = Contract.GetBFTAddress( + NeoToken.ShouldRefreshCommittee(height + 1, neoSystem.Settings.CommitteeMembersCount) ? + NativeContract.NEO.ComputeNextBlockValidators(Snapshot, neoSystem.Settings) : + NativeContract.NEO.GetNextBlockValidators(Snapshot, neoSystem.Settings.ValidatorsCount)) + } + }; + var pv = Validators; + Validators = NativeContract.NEO.GetNextBlockValidators(Snapshot, neoSystem.Settings.ValidatorsCount); + if (_witnessSize == 0 || (pv != null && pv.Length != Validators.Length)) + { + // Compute the expected size of the witness + using (ScriptBuilder sb = new()) + { + for (int x = 0; x < M; x++) + { + sb.EmitPush(new byte[64]); + } + _witnessSize = new Witness + { + InvocationScript = sb.ToArray(), + VerificationScript = Contract.CreateMultiSigRedeemScript(M, Validators) + }.Size; + } + } + MyIndex = -1; + ChangeViewPayloads = new ExtensiblePayload[Validators.Length]; + LastChangeViewPayloads = new ExtensiblePayload[Validators.Length]; + CommitPayloads = new ExtensiblePayload[Validators.Length]; + if (ValidatorsChanged || LastSeenMessage is null) + { + var previous_last_seen_message = LastSeenMessage; + LastSeenMessage = new Dictionary(); + foreach (var validator in Validators) + { + if (previous_last_seen_message != null && previous_last_seen_message.TryGetValue(validator, out var value)) + LastSeenMessage[validator] = value; + else + LastSeenMessage[validator] = height; + } + } + keyPair = null; + for (int i = 0; i < Validators.Length; i++) + { + WalletAccount account = wallet?.GetAccount(Validators[i]); + if (account?.HasKey != true) continue; + MyIndex = i; + keyPair = account.GetKey(); + break; + } + cachedMessages = new Dictionary(); + } + else + { + for (int i = 0; i < LastChangeViewPayloads.Length; i++) + if (GetMessage(ChangeViewPayloads[i])?.NewViewNumber >= viewNumber) + LastChangeViewPayloads[i] = ChangeViewPayloads[i]; + else + LastChangeViewPayloads[i] = null; + } + ViewNumber = viewNumber; + Block.Header.PrimaryIndex = GetPrimaryIndex(viewNumber); + Block.Header.MerkleRoot = null; + Block.Header.Timestamp = 0; + Block.Header.Nonce = 0; + Block.Transactions = null; + TransactionHashes = null; + PreparationPayloads = new ExtensiblePayload[Validators.Length]; + if (MyIndex >= 0) LastSeenMessage[Validators[MyIndex]] = Block.Index; + } + + public void Save() + { + store.PutSync(ConsensusStateKey, this.ToArray()); + } + + public void Deserialize(ref MemoryReader reader) + { + Reset(0); + if (reader.ReadUInt32() != Block.Version) throw new FormatException(); + if (reader.ReadUInt32() != Block.Index) throw new InvalidOperationException(); + Block.Header.Timestamp = reader.ReadUInt64(); + Block.Header.Nonce = reader.ReadUInt64(); + Block.Header.PrimaryIndex = reader.ReadByte(); + Block.Header.NextConsensus = reader.ReadSerializable(); + if (Block.NextConsensus.Equals(UInt160.Zero)) + Block.Header.NextConsensus = null; + ViewNumber = reader.ReadByte(); + TransactionHashes = reader.ReadSerializableArray(ushort.MaxValue); + Transaction[] transactions = reader.ReadSerializableArray(ushort.MaxValue); + PreparationPayloads = reader.ReadNullableArray(neoSystem.Settings.ValidatorsCount); + CommitPayloads = reader.ReadNullableArray(neoSystem.Settings.ValidatorsCount); + ChangeViewPayloads = reader.ReadNullableArray(neoSystem.Settings.ValidatorsCount); + LastChangeViewPayloads = reader.ReadNullableArray(neoSystem.Settings.ValidatorsCount); + if (TransactionHashes.Length == 0 && !RequestSentOrReceived) + TransactionHashes = null; + Transactions = transactions.Length == 0 && !RequestSentOrReceived ? null : transactions.ToDictionary(p => p.Hash); + VerificationContext = new TransactionVerificationContext(); + if (Transactions != null) + { + foreach (Transaction tx in Transactions.Values) + VerificationContext.AddTransaction(tx); + } + } + + public void Serialize(BinaryWriter writer) + { + writer.Write(Block.Version); + writer.Write(Block.Index); + writer.Write(Block.Timestamp); + writer.Write(Block.Nonce); + writer.Write(Block.PrimaryIndex); + writer.Write(Block.NextConsensus ?? UInt160.Zero); + writer.Write(ViewNumber); + writer.Write(TransactionHashes ?? Array.Empty()); + writer.Write(Transactions?.Values.ToArray() ?? Array.Empty()); + writer.WriteNullableArray(PreparationPayloads); + writer.WriteNullableArray(CommitPayloads); + writer.WriteNullableArray(ChangeViewPayloads); + writer.WriteNullableArray(LastChangeViewPayloads); + } + } +} diff --git a/src/Plugins/DBFTPlugin/Consensus/ConsensusService.Check.cs b/src/Plugins/DBFTPlugin/Consensus/ConsensusService.Check.cs new file mode 100644 index 0000000000..c6ba86ee86 --- /dev/null +++ b/src/Plugins/DBFTPlugin/Consensus/ConsensusService.Check.cs @@ -0,0 +1,104 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ConsensusService.Check.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Akka.Actor; +using Neo.IO; +using Neo.Network.P2P; +using Neo.Network.P2P.Payloads; +using Neo.Plugins.DBFTPlugin.Messages; +using Neo.Plugins.DBFTPlugin.Types; +using System; +using System.Linq; + +namespace Neo.Plugins.DBFTPlugin.Consensus +{ + partial class ConsensusService + { + private bool CheckPrepareResponse() + { + if (context.TransactionHashes.Length == context.Transactions.Count) + { + // if we are the primary for this view, but acting as a backup because we recovered our own + // previously sent prepare request, then we don't want to send a prepare response. + if (context.IsPrimary || context.WatchOnly) return true; + + // Check maximum block size via Native Contract policy + if (context.GetExpectedBlockSize() > dbftSettings.MaxBlockSize) + { + Log($"Rejected block: {context.Block.Index} The size exceed the policy", LogLevel.Warning); + RequestChangeView(ChangeViewReason.BlockRejectedByPolicy); + return false; + } + // Check maximum block system fee via Native Contract policy + if (context.GetExpectedBlockSystemFee() > dbftSettings.MaxBlockSystemFee) + { + Log($"Rejected block: {context.Block.Index} The system fee exceed the policy", LogLevel.Warning); + RequestChangeView(ChangeViewReason.BlockRejectedByPolicy); + return false; + } + + // Timeout extension due to prepare response sent + // around 2*15/M=30.0/5 ~ 40% block time (for M=5) + ExtendTimerByFactor(2); + + Log($"Sending {nameof(PrepareResponse)}"); + localNode.Tell(new LocalNode.SendDirectly { Inventory = context.MakePrepareResponse() }); + CheckPreparations(); + } + return true; + } + + private void CheckCommits() + { + if (context.CommitPayloads.Count(p => context.GetMessage(p)?.ViewNumber == context.ViewNumber) >= context.M && context.TransactionHashes.All(p => context.Transactions.ContainsKey(p))) + { + block_received_index = context.Block.Index; + block_received_time = TimeProvider.Current.UtcNow; + Block block = context.CreateBlock(); + Log($"Sending {nameof(Block)}: height={block.Index} hash={block.Hash} tx={block.Transactions.Length}"); + blockchain.Tell(block); + } + } + + private void CheckExpectedView(byte viewNumber) + { + if (context.ViewNumber >= viewNumber) return; + var messages = context.ChangeViewPayloads.Select(p => context.GetMessage(p)).ToArray(); + // if there are `M` change view payloads with NewViewNumber greater than viewNumber, then, it is safe to move + if (messages.Count(p => p != null && p.NewViewNumber >= viewNumber) >= context.M) + { + if (!context.WatchOnly) + { + ChangeView message = messages[context.MyIndex]; + // Communicate the network about my agreement to move to `viewNumber` + // if my last change view payload, `message`, has NewViewNumber lower than current view to change + if (message is null || message.NewViewNumber < viewNumber) + localNode.Tell(new LocalNode.SendDirectly { Inventory = context.MakeChangeView(ChangeViewReason.ChangeAgreement) }); + } + InitializeConsensus(viewNumber); + } + } + + private void CheckPreparations() + { + if (context.PreparationPayloads.Count(p => p != null) >= context.M && context.TransactionHashes.All(p => context.Transactions.ContainsKey(p))) + { + ExtensiblePayload payload = context.MakeCommit(); + Log($"Sending {nameof(Commit)}"); + context.Save(); + localNode.Tell(new LocalNode.SendDirectly { Inventory = payload }); + // Set timer, so we will resend the commit in case of a networking issue + ChangeTimer(TimeSpan.FromMilliseconds(neoSystem.Settings.MillisecondsPerBlock)); + CheckCommits(); + } + } + } +} diff --git a/src/Plugins/DBFTPlugin/Consensus/ConsensusService.OnMessage.cs b/src/Plugins/DBFTPlugin/Consensus/ConsensusService.OnMessage.cs new file mode 100644 index 0000000000..3c6ab8e243 --- /dev/null +++ b/src/Plugins/DBFTPlugin/Consensus/ConsensusService.OnMessage.cs @@ -0,0 +1,316 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ConsensusService.OnMessage.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Akka.Actor; +using Neo.Cryptography; +using Neo.Ledger; +using Neo.Network.P2P; +using Neo.Network.P2P.Payloads; +using Neo.Plugins.DBFTPlugin.Messages; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Neo.Plugins.DBFTPlugin.Consensus +{ + partial class ConsensusService + { + private void OnConsensusPayload(ExtensiblePayload payload) + { + if (context.BlockSent) return; + ConsensusMessage message; + try + { + message = context.GetMessage(payload); + } + catch (Exception ex) + { + Utility.Log(nameof(ConsensusService), LogLevel.Debug, ex.ToString()); + return; + } + + if (!message.Verify(neoSystem.Settings)) return; + if (message.BlockIndex != context.Block.Index) + { + if (context.Block.Index < message.BlockIndex) + { + Log($"Chain is behind: expected={message.BlockIndex} current={context.Block.Index - 1}", LogLevel.Warning); + } + return; + } + if (message.ValidatorIndex >= context.Validators.Length) return; + if (payload.Sender != Contract.CreateSignatureRedeemScript(context.Validators[message.ValidatorIndex]).ToScriptHash()) return; + context.LastSeenMessage[context.Validators[message.ValidatorIndex]] = message.BlockIndex; + switch (message) + { + case PrepareRequest request: + OnPrepareRequestReceived(payload, request); + break; + case PrepareResponse response: + OnPrepareResponseReceived(payload, response); + break; + case ChangeView view: + OnChangeViewReceived(payload, view); + break; + case Commit commit: + OnCommitReceived(payload, commit); + break; + case RecoveryRequest request: + OnRecoveryRequestReceived(payload, request); + break; + case RecoveryMessage recovery: + OnRecoveryMessageReceived(recovery); + break; + } + } + + private void OnPrepareRequestReceived(ExtensiblePayload payload, PrepareRequest message) + { + if (context.RequestSentOrReceived || context.NotAcceptingPayloadsDueToViewChanging) return; + if (message.ValidatorIndex != context.Block.PrimaryIndex || message.ViewNumber != context.ViewNumber) return; + if (message.Version != context.Block.Version || message.PrevHash != context.Block.PrevHash) return; + if (message.TransactionHashes.Length > neoSystem.Settings.MaxTransactionsPerBlock) return; + Log($"{nameof(OnPrepareRequestReceived)}: height={message.BlockIndex} view={message.ViewNumber} index={message.ValidatorIndex} tx={message.TransactionHashes.Length}"); + if (message.Timestamp <= context.PrevHeader.Timestamp || message.Timestamp > TimeProvider.Current.UtcNow.AddMilliseconds(8 * neoSystem.Settings.MillisecondsPerBlock).ToTimestampMS()) + { + Log($"Timestamp incorrect: {message.Timestamp}", LogLevel.Warning); + return; + } + + if (message.TransactionHashes.Any(p => NativeContract.Ledger.ContainsTransaction(context.Snapshot, p))) + { + Log($"Invalid request: transaction already exists", LogLevel.Warning); + return; + } + + // Timeout extension: prepare request has been received with success + // around 2*15/M=30.0/5 ~ 40% block time (for M=5) + ExtendTimerByFactor(2); + + context.Block.Header.Timestamp = message.Timestamp; + context.Block.Header.Nonce = message.Nonce; + context.TransactionHashes = message.TransactionHashes; + + context.Transactions = new Dictionary(); + context.VerificationContext = new TransactionVerificationContext(); + for (int i = 0; i < context.PreparationPayloads.Length; i++) + if (context.PreparationPayloads[i] != null) + if (!context.GetMessage(context.PreparationPayloads[i]).PreparationHash.Equals(payload.Hash)) + context.PreparationPayloads[i] = null; + context.PreparationPayloads[message.ValidatorIndex] = payload; + byte[] hashData = context.EnsureHeader().GetSignData(neoSystem.Settings.Network); + for (int i = 0; i < context.CommitPayloads.Length; i++) + if (context.GetMessage(context.CommitPayloads[i])?.ViewNumber == context.ViewNumber) + if (!Crypto.VerifySignature(hashData, context.GetMessage(context.CommitPayloads[i]).Signature.Span, context.Validators[i])) + context.CommitPayloads[i] = null; + + if (context.TransactionHashes.Length == 0) + { + // There are no tx so we should act like if all the transactions were filled + CheckPrepareResponse(); + return; + } + + Dictionary mempoolVerified = neoSystem.MemPool.GetVerifiedTransactions().ToDictionary(p => p.Hash); + List unverified = new List(); + foreach (UInt256 hash in context.TransactionHashes) + { + if (mempoolVerified.TryGetValue(hash, out Transaction tx)) + { + if (NativeContract.Ledger.ContainsConflictHash(context.Snapshot, hash, tx.Signers.Select(s => s.Account), neoSystem.Settings.MaxTraceableBlocks)) + { + Log($"Invalid request: transaction has on-chain conflict", LogLevel.Warning); + return; + } + + if (!AddTransaction(tx, false)) + return; + } + else + { + if (neoSystem.MemPool.TryGetValue(hash, out tx)) + { + if (NativeContract.Ledger.ContainsConflictHash(context.Snapshot, hash, tx.Signers.Select(s => s.Account), neoSystem.Settings.MaxTraceableBlocks)) + { + Log($"Invalid request: transaction has on-chain conflict", LogLevel.Warning); + return; + } + unverified.Add(tx); + } + } + } + foreach (Transaction tx in unverified) + if (!AddTransaction(tx, true)) + return; + if (context.Transactions.Count < context.TransactionHashes.Length) + { + UInt256[] hashes = context.TransactionHashes.Where(i => !context.Transactions.ContainsKey(i)).ToArray(); + taskManager.Tell(new TaskManager.RestartTasks + { + Payload = InvPayload.Create(InventoryType.TX, hashes) + }); + } + } + + private void OnPrepareResponseReceived(ExtensiblePayload payload, PrepareResponse message) + { + if (message.ViewNumber != context.ViewNumber) return; + if (context.PreparationPayloads[message.ValidatorIndex] != null || context.NotAcceptingPayloadsDueToViewChanging) return; + if (context.PreparationPayloads[context.Block.PrimaryIndex] != null && !message.PreparationHash.Equals(context.PreparationPayloads[context.Block.PrimaryIndex].Hash)) + return; + + // Timeout extension: prepare response has been received with success + // around 2*15/M=30.0/5 ~ 40% block time (for M=5) + ExtendTimerByFactor(2); + + Log($"{nameof(OnPrepareResponseReceived)}: height={message.BlockIndex} view={message.ViewNumber} index={message.ValidatorIndex}"); + context.PreparationPayloads[message.ValidatorIndex] = payload; + if (context.WatchOnly || context.CommitSent) return; + if (context.RequestSentOrReceived) + CheckPreparations(); + } + + private void OnChangeViewReceived(ExtensiblePayload payload, ChangeView message) + { + if (message.NewViewNumber <= context.ViewNumber) + OnRecoveryRequestReceived(payload, message); + + if (context.CommitSent) return; + + var expectedView = context.GetMessage(context.ChangeViewPayloads[message.ValidatorIndex])?.NewViewNumber ?? 0; + if (message.NewViewNumber <= expectedView) + return; + + Log($"{nameof(OnChangeViewReceived)}: height={message.BlockIndex} view={message.ViewNumber} index={message.ValidatorIndex} nv={message.NewViewNumber} reason={message.Reason}"); + context.ChangeViewPayloads[message.ValidatorIndex] = payload; + CheckExpectedView(message.NewViewNumber); + } + + private void OnCommitReceived(ExtensiblePayload payload, Commit commit) + { + ref ExtensiblePayload existingCommitPayload = ref context.CommitPayloads[commit.ValidatorIndex]; + if (existingCommitPayload != null) + { + if (existingCommitPayload.Hash != payload.Hash) + Log($"Rejected {nameof(Commit)}: height={commit.BlockIndex} index={commit.ValidatorIndex} view={commit.ViewNumber} existingView={context.GetMessage(existingCommitPayload).ViewNumber}", LogLevel.Warning); + return; + } + + if (commit.ViewNumber == context.ViewNumber) + { + // Timeout extension: commit has been received with success + // around 4*15s/M=60.0s/5=12.0s ~ 80% block time (for M=5) + ExtendTimerByFactor(4); + + Log($"{nameof(OnCommitReceived)}: height={commit.BlockIndex} view={commit.ViewNumber} index={commit.ValidatorIndex} nc={context.CountCommitted} nf={context.CountFailed}"); + + byte[] hashData = context.EnsureHeader()?.GetSignData(neoSystem.Settings.Network); + if (hashData == null) + { + existingCommitPayload = payload; + } + else if (Crypto.VerifySignature(hashData, commit.Signature.Span, context.Validators[commit.ValidatorIndex])) + { + existingCommitPayload = payload; + CheckCommits(); + } + return; + } + else + { + // Receiving commit from another view + existingCommitPayload = payload; + } + } + + private void OnRecoveryMessageReceived(RecoveryMessage message) + { + // isRecovering is always set to false again after OnRecoveryMessageReceived + isRecovering = true; + int validChangeViews = 0, totalChangeViews = 0, validPrepReq = 0, totalPrepReq = 0; + int validPrepResponses = 0, totalPrepResponses = 0, validCommits = 0, totalCommits = 0; + + Log($"{nameof(OnRecoveryMessageReceived)}: height={message.BlockIndex} view={message.ViewNumber} index={message.ValidatorIndex}"); + try + { + if (message.ViewNumber > context.ViewNumber) + { + if (context.CommitSent) return; + ExtensiblePayload[] changeViewPayloads = message.GetChangeViewPayloads(context); + totalChangeViews = changeViewPayloads.Length; + foreach (ExtensiblePayload changeViewPayload in changeViewPayloads) + if (ReverifyAndProcessPayload(changeViewPayload)) validChangeViews++; + } + if (message.ViewNumber == context.ViewNumber && !context.NotAcceptingPayloadsDueToViewChanging && !context.CommitSent) + { + if (!context.RequestSentOrReceived) + { + ExtensiblePayload prepareRequestPayload = message.GetPrepareRequestPayload(context); + if (prepareRequestPayload != null) + { + totalPrepReq = 1; + if (ReverifyAndProcessPayload(prepareRequestPayload)) validPrepReq++; + } + } + ExtensiblePayload[] prepareResponsePayloads = message.GetPrepareResponsePayloads(context); + totalPrepResponses = prepareResponsePayloads.Length; + foreach (ExtensiblePayload prepareResponsePayload in prepareResponsePayloads) + if (ReverifyAndProcessPayload(prepareResponsePayload)) validPrepResponses++; + } + if (message.ViewNumber <= context.ViewNumber) + { + // Ensure we know about all commits from lower view numbers. + ExtensiblePayload[] commitPayloads = message.GetCommitPayloadsFromRecoveryMessage(context); + totalCommits = commitPayloads.Length; + foreach (ExtensiblePayload commitPayload in commitPayloads) + if (ReverifyAndProcessPayload(commitPayload)) validCommits++; + } + } + finally + { + Log($"Recovery finished: (valid/total) ChgView: {validChangeViews}/{totalChangeViews} PrepReq: {validPrepReq}/{totalPrepReq} PrepResp: {validPrepResponses}/{totalPrepResponses} Commits: {validCommits}/{totalCommits}"); + isRecovering = false; + } + } + + private void OnRecoveryRequestReceived(ExtensiblePayload payload, ConsensusMessage message) + { + // We keep track of the payload hashes received in this block, and don't respond with recovery + // in response to the same payload that we already responded to previously. + // ChangeView messages include a Timestamp when the change view is sent, thus if a node restarts + // and issues a change view for the same view, it will have a different hash and will correctly respond + // again; however replay attacks of the ChangeView message from arbitrary nodes will not trigger an + // additional recovery message response. + if (!knownHashes.Add(payload.Hash)) return; + + Log($"{nameof(OnRecoveryRequestReceived)}: height={message.BlockIndex} index={message.ValidatorIndex} view={message.ViewNumber}"); + if (context.WatchOnly) return; + if (!context.CommitSent) + { + bool shouldSendRecovery = false; + int allowedRecoveryNodeCount = context.F + 1; + // Limit recoveries to be sent from an upper limit of `f + 1` nodes + for (int i = 1; i <= allowedRecoveryNodeCount; i++) + { + var chosenIndex = (message.ValidatorIndex + i) % context.Validators.Length; + if (chosenIndex != context.MyIndex) continue; + shouldSendRecovery = true; + break; + } + + if (!shouldSendRecovery) return; + } + localNode.Tell(new LocalNode.SendDirectly { Inventory = context.MakeRecoveryMessage() }); + } + } +} diff --git a/src/Plugins/DBFTPlugin/Consensus/ConsensusService.cs b/src/Plugins/DBFTPlugin/Consensus/ConsensusService.cs new file mode 100644 index 0000000000..eb97e7024b --- /dev/null +++ b/src/Plugins/DBFTPlugin/Consensus/ConsensusService.cs @@ -0,0 +1,346 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ConsensusService.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Akka.Actor; +using Neo.IO; +using Neo.Ledger; +using Neo.Network.P2P; +using Neo.Network.P2P.Payloads; +using Neo.Plugins.DBFTPlugin.Messages; +using Neo.Plugins.DBFTPlugin.Types; +using Neo.Wallets; +using System; +using System.Collections.Generic; +using System.Linq; +using static Neo.Ledger.Blockchain; + +namespace Neo.Plugins.DBFTPlugin.Consensus +{ + partial class ConsensusService : UntypedActor + { + public class Start { } + private class Timer { public uint Height; public byte ViewNumber; } + + private readonly ConsensusContext context; + private readonly IActorRef localNode; + private readonly IActorRef taskManager; + private readonly IActorRef blockchain; + private ICancelable timer_token; + private DateTime block_received_time; + private uint block_received_index; + private bool started = false; + + /// + /// This will record the information from last scheduled timer + /// + private DateTime clock_started = TimeProvider.Current.UtcNow; + private TimeSpan expected_delay = TimeSpan.Zero; + + /// + /// This will be cleared every block (so it will not grow out of control, but is used to prevent repeatedly + /// responding to the same message. + /// + private readonly HashSet knownHashes = new(); + /// + /// This variable is only true during OnRecoveryMessageReceived + /// + private bool isRecovering = false; + private readonly Settings dbftSettings; + private readonly NeoSystem neoSystem; + + public ConsensusService(NeoSystem neoSystem, Settings settings, Wallet wallet) + : this(neoSystem, settings, new ConsensusContext(neoSystem, settings, wallet)) { } + + internal ConsensusService(NeoSystem neoSystem, Settings settings, ConsensusContext context) + { + this.neoSystem = neoSystem; + localNode = neoSystem.LocalNode; + taskManager = neoSystem.TaskManager; + blockchain = neoSystem.Blockchain; + dbftSettings = settings; + this.context = context; + Context.System.EventStream.Subscribe(Self, typeof(Blockchain.PersistCompleted)); + Context.System.EventStream.Subscribe(Self, typeof(Blockchain.RelayResult)); + } + + private void OnPersistCompleted(Block block) + { + Log($"Persisted {nameof(Block)}: height={block.Index} hash={block.Hash} tx={block.Transactions.Length} nonce={block.Nonce}"); + knownHashes.Clear(); + InitializeConsensus(0); + } + + private void InitializeConsensus(byte viewNumber) + { + context.Reset(viewNumber); + if (viewNumber > 0) + Log($"View changed: view={viewNumber} primary={context.Validators[context.GetPrimaryIndex((byte)(viewNumber - 1u))]}", LogLevel.Warning); + Log($"Initialize: height={context.Block.Index} view={viewNumber} index={context.MyIndex} role={(context.IsPrimary ? "Primary" : context.WatchOnly ? "WatchOnly" : "Backup")}"); + if (context.WatchOnly) return; + if (context.IsPrimary) + { + if (isRecovering) + { + ChangeTimer(TimeSpan.FromMilliseconds(neoSystem.Settings.MillisecondsPerBlock << (viewNumber + 1))); + } + else + { + TimeSpan span = neoSystem.Settings.TimePerBlock; + if (block_received_index + 1 == context.Block.Index) + { + var diff = TimeProvider.Current.UtcNow - block_received_time; + if (diff >= span) + span = TimeSpan.Zero; + else + span -= diff; + } + ChangeTimer(span); + } + } + else + { + ChangeTimer(TimeSpan.FromMilliseconds(neoSystem.Settings.MillisecondsPerBlock << (viewNumber + 1))); + } + } + + protected override void OnReceive(object message) + { + if (message is Start) + { + if (started) return; + OnStart(); + } + else + { + if (!started) return; + switch (message) + { + case Timer timer: + OnTimer(timer); + break; + case Transaction transaction: + OnTransaction(transaction); + break; + case Blockchain.PersistCompleted completed: + OnPersistCompleted(completed.Block); + break; + case Blockchain.RelayResult rr: + if (rr.Result == VerifyResult.Succeed && rr.Inventory is ExtensiblePayload payload && payload.Category == "dBFT") + OnConsensusPayload(payload); + break; + } + } + } + + private void OnStart() + { + Log("OnStart"); + started = true; + if (!dbftSettings.IgnoreRecoveryLogs && context.Load()) + { + if (context.Transactions != null) + { + blockchain.Ask(new Blockchain.FillMemoryPool + { + Transactions = context.Transactions.Values + }).Wait(); + } + if (context.CommitSent) + { + CheckPreparations(); + return; + } + } + InitializeConsensus(context.ViewNumber); + // Issue a recovery request on start-up in order to possibly catch up with other nodes + if (!context.WatchOnly) + RequestRecovery(); + } + + private void OnTimer(Timer timer) + { + if (context.WatchOnly || context.BlockSent) return; + if (timer.Height != context.Block.Index || timer.ViewNumber != context.ViewNumber) return; + if (context.IsPrimary && !context.RequestSentOrReceived) + { + SendPrepareRequest(); + } + else if ((context.IsPrimary && context.RequestSentOrReceived) || context.IsBackup) + { + if (context.CommitSent) + { + // Re-send commit periodically by sending recover message in case of a network issue. + Log($"Sending {nameof(RecoveryMessage)} to resend {nameof(Commit)}"); + localNode.Tell(new LocalNode.SendDirectly { Inventory = context.MakeRecoveryMessage() }); + ChangeTimer(TimeSpan.FromMilliseconds(neoSystem.Settings.MillisecondsPerBlock << 1)); + } + else + { + var reason = ChangeViewReason.Timeout; + + if (context.Block != null && context.TransactionHashes?.Length > context.Transactions?.Count) + { + reason = ChangeViewReason.TxNotFound; + } + + RequestChangeView(reason); + } + } + } + + private void SendPrepareRequest() + { + Log($"Sending {nameof(PrepareRequest)}: height={context.Block.Index} view={context.ViewNumber}"); + localNode.Tell(new LocalNode.SendDirectly { Inventory = context.MakePrepareRequest() }); + + if (context.Validators.Length == 1) + CheckPreparations(); + + if (context.TransactionHashes.Length > 0) + { + foreach (InvPayload payload in InvPayload.CreateGroup(InventoryType.TX, context.TransactionHashes)) + localNode.Tell(Message.Create(MessageCommand.Inv, payload)); + } + ChangeTimer(TimeSpan.FromMilliseconds((neoSystem.Settings.MillisecondsPerBlock << (context.ViewNumber + 1)) - (context.ViewNumber == 0 ? neoSystem.Settings.MillisecondsPerBlock : 0))); + } + + private void RequestRecovery() + { + Log($"Sending {nameof(RecoveryRequest)}: height={context.Block.Index} view={context.ViewNumber} nc={context.CountCommitted} nf={context.CountFailed}"); + localNode.Tell(new LocalNode.SendDirectly { Inventory = context.MakeRecoveryRequest() }); + } + + private void RequestChangeView(ChangeViewReason reason) + { + if (context.WatchOnly) return; + // Request for next view is always one view more than the current context.ViewNumber + // Nodes will not contribute for changing to a view higher than (context.ViewNumber+1), unless they are recovered + // The latter may happen by nodes in higher views with, at least, `M` proofs + byte expectedView = context.ViewNumber; + expectedView++; + ChangeTimer(TimeSpan.FromMilliseconds(neoSystem.Settings.MillisecondsPerBlock << (expectedView + 1))); + if ((context.CountCommitted + context.CountFailed) > context.F) + { + RequestRecovery(); + } + else + { + Log($"Sending {nameof(ChangeView)}: height={context.Block.Index} view={context.ViewNumber} nv={expectedView} nc={context.CountCommitted} nf={context.CountFailed} reason={reason}"); + localNode.Tell(new LocalNode.SendDirectly { Inventory = context.MakeChangeView(reason) }); + CheckExpectedView(expectedView); + } + } + + private bool ReverifyAndProcessPayload(ExtensiblePayload payload) + { + RelayResult relayResult = blockchain.Ask(new Blockchain.Reverify { Inventories = new IInventory[] { payload } }).Result; + if (relayResult.Result != VerifyResult.Succeed) return false; + OnConsensusPayload(payload); + return true; + } + + private void OnTransaction(Transaction transaction) + { + if (!context.IsBackup || context.NotAcceptingPayloadsDueToViewChanging || !context.RequestSentOrReceived || context.ResponseSent || context.BlockSent) + return; + if (context.Transactions.ContainsKey(transaction.Hash)) return; + if (!context.TransactionHashes.Contains(transaction.Hash)) return; + AddTransaction(transaction, true); + } + + private bool AddTransaction(Transaction tx, bool verify) + { + if (verify) + { + // At this step we're sure that there's no on-chain transaction that conflicts with + // the provided tx because of the previous Blockchain's OnReceive check. Thus, we only + // need to check that current context doesn't contain conflicting transactions. + VerifyResult result; + + // Firstly, check whether tx has Conlicts attribute with the hash of one of the context's transactions. + foreach (var h in tx.GetAttributes().Select(attr => attr.Hash)) + { + if (context.TransactionHashes.Contains(h)) + { + result = VerifyResult.HasConflicts; + Log($"Rejected tx: {tx.Hash}, {result}{Environment.NewLine}{tx.ToArray().ToHexString()}", LogLevel.Warning); + RequestChangeView(ChangeViewReason.TxInvalid); + return false; + } + } + // After that, check whether context's transactions have Conflicts attribute with tx's hash. + foreach (var pooledTx in context.Transactions.Values) + { + if (pooledTx.GetAttributes().Select(attr => attr.Hash).Contains(tx.Hash)) + { + result = VerifyResult.HasConflicts; + Log($"Rejected tx: {tx.Hash}, {result}{Environment.NewLine}{tx.ToArray().ToHexString()}", LogLevel.Warning); + RequestChangeView(ChangeViewReason.TxInvalid); + return false; + } + } + + // We've ensured that there's no conlicting transactions in the context, thus, can safely provide an empty conflicting list + // for futher verification. + var conflictingTxs = new List(); + result = tx.Verify(neoSystem.Settings, context.Snapshot, context.VerificationContext, conflictingTxs); + if (result != VerifyResult.Succeed) + { + Log($"Rejected tx: {tx.Hash}, {result}{Environment.NewLine}{tx.ToArray().ToHexString()}", LogLevel.Warning); + RequestChangeView(result == VerifyResult.PolicyFail ? ChangeViewReason.TxRejectedByPolicy : ChangeViewReason.TxInvalid); + return false; + } + } + context.Transactions[tx.Hash] = tx; + context.VerificationContext.AddTransaction(tx); + return CheckPrepareResponse(); + } + + private void ChangeTimer(TimeSpan delay) + { + clock_started = TimeProvider.Current.UtcNow; + expected_delay = delay; + timer_token.CancelIfNotNull(); + timer_token = Context.System.Scheduler.ScheduleTellOnceCancelable(delay, Self, new Timer + { + Height = context.Block.Index, + ViewNumber = context.ViewNumber + }, ActorRefs.NoSender); + } + + // this function increases existing timer (never decreases) with a value proportional to `maxDelayInBlockTimes`*`Blockchain.MillisecondsPerBlock` + private void ExtendTimerByFactor(int maxDelayInBlockTimes) + { + TimeSpan nextDelay = expected_delay - (TimeProvider.Current.UtcNow - clock_started) + TimeSpan.FromMilliseconds(maxDelayInBlockTimes * neoSystem.Settings.MillisecondsPerBlock / (double)context.M); + if (!context.WatchOnly && !context.ViewChanging && !context.CommitSent && (nextDelay > TimeSpan.Zero)) + ChangeTimer(nextDelay); + } + + protected override void PostStop() + { + Log("OnStop"); + started = false; + Context.System.EventStream.Unsubscribe(Self); + context.Dispose(); + base.PostStop(); + } + + public static Props Props(NeoSystem neoSystem, Settings dbftSettings, Wallet wallet) + { + return Akka.Actor.Props.Create(() => new ConsensusService(neoSystem, dbftSettings, wallet)); + } + + private static void Log(string message, LogLevel level = LogLevel.Info) + { + Utility.Log(nameof(ConsensusService), level, message); + } + } +} diff --git a/src/Plugins/DBFTPlugin/DBFTPlugin.cs b/src/Plugins/DBFTPlugin/DBFTPlugin.cs new file mode 100644 index 0000000000..9ca44adc74 --- /dev/null +++ b/src/Plugins/DBFTPlugin/DBFTPlugin.cs @@ -0,0 +1,104 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// DBFTPlugin.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Akka.Actor; +using Neo.ConsoleService; +using Neo.Network.P2P; +using Neo.Network.P2P.Payloads; +using Neo.Plugins; +using Neo.Plugins.DBFTPlugin.Consensus; +using Neo.Wallets; + +namespace Neo.Plugins.DBFTPlugin +{ + public class DBFTPlugin : Plugin + { + private IWalletProvider walletProvider; + private IActorRef consensus; + private bool started = false; + private NeoSystem neoSystem; + private Settings settings; + + public override string Description => "Consensus plugin with dBFT algorithm."; + + public override string ConfigFile => System.IO.Path.Combine(RootPath, "DBFTPlugin.json"); + + public DBFTPlugin() + { + RemoteNode.MessageReceived += RemoteNode_MessageReceived; + } + + public DBFTPlugin(Settings settings) : this() + { + this.settings = settings; + } + + public override void Dispose() + { + RemoteNode.MessageReceived -= RemoteNode_MessageReceived; + } + + protected override void Configure() + { + settings ??= new Settings(GetConfiguration()); + } + + protected override void OnSystemLoaded(NeoSystem system) + { + if (system.Settings.Network != settings.Network) return; + neoSystem = system; + neoSystem.ServiceAdded += NeoSystem_ServiceAdded; + } + + private void NeoSystem_ServiceAdded(object sender, object service) + { + if (service is not IWalletProvider provider) return; + walletProvider = provider; + neoSystem.ServiceAdded -= NeoSystem_ServiceAdded; + if (settings.AutoStart) + { + walletProvider.WalletChanged += WalletProvider_WalletChanged; + } + } + + private void WalletProvider_WalletChanged(object sender, Wallet wallet) + { + walletProvider.WalletChanged -= WalletProvider_WalletChanged; + Start(wallet); + } + + [ConsoleCommand("start consensus", Category = "Consensus", Description = "Start consensus service (dBFT)")] + private void OnStart() + { + Start(walletProvider.GetWallet()); + } + + public void Start(Wallet wallet) + { + if (started) return; + started = true; + consensus = neoSystem.ActorSystem.ActorOf(ConsensusService.Props(neoSystem, settings, wallet)); + consensus.Tell(new ConsensusService.Start()); + } + + private bool RemoteNode_MessageReceived(NeoSystem system, Message message) + { + if (message.Command == MessageCommand.Transaction) + { + Transaction tx = (Transaction)message.Payload; + if (tx.SystemFee > settings.MaxBlockSystemFee) + return false; + consensus?.Tell(tx); + } + return true; + } + } +} diff --git a/src/Plugins/DBFTPlugin/DBFTPlugin.csproj b/src/Plugins/DBFTPlugin/DBFTPlugin.csproj new file mode 100644 index 0000000000..93c77ad1f8 --- /dev/null +++ b/src/Plugins/DBFTPlugin/DBFTPlugin.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + Neo.Consensus.DBFT + Neo.Consensus + ../../../bin/$(PackageId) + + + + + + + + + PreserveNewest + + + + diff --git a/src/Plugins/DBFTPlugin/DBFTPlugin.json b/src/Plugins/DBFTPlugin/DBFTPlugin.json new file mode 100644 index 0000000000..2e2b710ba3 --- /dev/null +++ b/src/Plugins/DBFTPlugin/DBFTPlugin.json @@ -0,0 +1,10 @@ +{ + "PluginConfiguration": { + "RecoveryLogs": "ConsensusState", + "IgnoreRecoveryLogs": false, + "AutoStart": false, + "Network": 860833102, + "MaxBlockSize": 2097152, + "MaxBlockSystemFee": 150000000000 + } +} diff --git a/src/Plugins/DBFTPlugin/Messages/ChangeView.cs b/src/Plugins/DBFTPlugin/Messages/ChangeView.cs new file mode 100644 index 0000000000..84f681f499 --- /dev/null +++ b/src/Plugins/DBFTPlugin/Messages/ChangeView.cs @@ -0,0 +1,57 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ChangeView.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using Neo.Plugins.DBFTPlugin.Types; +using System.IO; + +namespace Neo.Plugins.DBFTPlugin.Messages +{ + public class ChangeView : ConsensusMessage + { + /// + /// NewViewNumber is always set to the current ViewNumber asking changeview + 1 + /// + public byte NewViewNumber => (byte)(ViewNumber + 1); + + /// + /// Timestamp of when the ChangeView message was created. This allows receiving nodes to ensure + /// they only respond once to a specific ChangeView request (it thus prevents replay of the ChangeView + /// message from repeatedly broadcasting RecoveryMessages). + /// + public ulong Timestamp; + + /// + /// Reason + /// + public ChangeViewReason Reason; + + public override int Size => base.Size + + sizeof(ulong) + // Timestamp + sizeof(ChangeViewReason); // Reason + + public ChangeView() : base(ConsensusMessageType.ChangeView) { } + + public override void Deserialize(ref MemoryReader reader) + { + base.Deserialize(ref reader); + Timestamp = reader.ReadUInt64(); + Reason = (ChangeViewReason)reader.ReadByte(); + } + + public override void Serialize(BinaryWriter writer) + { + base.Serialize(writer); + writer.Write(Timestamp); + writer.Write((byte)Reason); + } + } +} diff --git a/src/Plugins/DBFTPlugin/Messages/Commit.cs b/src/Plugins/DBFTPlugin/Messages/Commit.cs new file mode 100644 index 0000000000..8f276bb6fe --- /dev/null +++ b/src/Plugins/DBFTPlugin/Messages/Commit.cs @@ -0,0 +1,39 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Commit.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using Neo.Plugins.DBFTPlugin.Types; +using System; +using System.IO; + +namespace Neo.Plugins.DBFTPlugin.Messages +{ + public class Commit : ConsensusMessage + { + public ReadOnlyMemory Signature; + + public override int Size => base.Size + Signature.Length; + + public Commit() : base(ConsensusMessageType.Commit) { } + + public override void Deserialize(ref MemoryReader reader) + { + base.Deserialize(ref reader); + Signature = reader.ReadMemory(64); + } + + public override void Serialize(BinaryWriter writer) + { + base.Serialize(writer); + writer.Write(Signature.Span); + } + } +} diff --git a/src/Plugins/DBFTPlugin/Messages/ConsensusMessage.cs b/src/Plugins/DBFTPlugin/Messages/ConsensusMessage.cs new file mode 100644 index 0000000000..de030d166d --- /dev/null +++ b/src/Plugins/DBFTPlugin/Messages/ConsensusMessage.cs @@ -0,0 +1,70 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ConsensusMessage.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using Neo.Plugins.DBFTPlugin.Types; +using System; +using System.IO; + +namespace Neo.Plugins.DBFTPlugin.Messages +{ + public abstract class ConsensusMessage : ISerializable + { + public readonly ConsensusMessageType Type; + public uint BlockIndex; + public byte ValidatorIndex; + public byte ViewNumber; + + public virtual int Size => + sizeof(ConsensusMessageType) + //Type + sizeof(uint) + //BlockIndex + sizeof(byte) + //ValidatorIndex + sizeof(byte); //ViewNumber + + protected ConsensusMessage(ConsensusMessageType type) + { + if (!Enum.IsDefined(typeof(ConsensusMessageType), type)) + throw new ArgumentOutOfRangeException(nameof(type)); + Type = type; + } + + public virtual void Deserialize(ref MemoryReader reader) + { + if (Type != (ConsensusMessageType)reader.ReadByte()) + throw new FormatException(); + BlockIndex = reader.ReadUInt32(); + ValidatorIndex = reader.ReadByte(); + ViewNumber = reader.ReadByte(); + } + + public static ConsensusMessage DeserializeFrom(ReadOnlyMemory data) + { + ConsensusMessageType type = (ConsensusMessageType)data.Span[0]; + Type t = typeof(ConsensusMessage); + t = t.Assembly.GetType($"{t.Namespace}.{type}", false); + if (t is null) throw new FormatException(); + return (ConsensusMessage)data.AsSerializable(t); + } + + public virtual bool Verify(ProtocolSettings protocolSettings) + { + return ValidatorIndex < protocolSettings.ValidatorsCount; + } + + public virtual void Serialize(BinaryWriter writer) + { + writer.Write((byte)Type); + writer.Write(BlockIndex); + writer.Write(ValidatorIndex); + writer.Write(ViewNumber); + } + } +} diff --git a/src/Plugins/DBFTPlugin/Messages/PrepareRequest.cs b/src/Plugins/DBFTPlugin/Messages/PrepareRequest.cs new file mode 100644 index 0000000000..495ccbd726 --- /dev/null +++ b/src/Plugins/DBFTPlugin/Messages/PrepareRequest.cs @@ -0,0 +1,65 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// PrepareRequest.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using Neo.Plugins.DBFTPlugin.Types; +using System; +using System.IO; +using System.Linq; + +namespace Neo.Plugins.DBFTPlugin.Messages +{ + public class PrepareRequest : ConsensusMessage + { + public uint Version; + public UInt256 PrevHash; + public ulong Timestamp; + public ulong Nonce; + public UInt256[] TransactionHashes; + + public override int Size => base.Size + + sizeof(uint) //Version + + UInt256.Length //PrevHash + + sizeof(ulong) //Timestamp + + sizeof(ulong) // Nonce + + TransactionHashes.GetVarSize(); //TransactionHashes + + public PrepareRequest() : base(ConsensusMessageType.PrepareRequest) { } + + public override void Deserialize(ref MemoryReader reader) + { + base.Deserialize(ref reader); + Version = reader.ReadUInt32(); + PrevHash = reader.ReadSerializable(); + Timestamp = reader.ReadUInt64(); + Nonce = reader.ReadUInt64(); + TransactionHashes = reader.ReadSerializableArray(ushort.MaxValue); + if (TransactionHashes.Distinct().Count() != TransactionHashes.Length) + throw new FormatException(); + } + + public override bool Verify(ProtocolSettings protocolSettings) + { + if (!base.Verify(protocolSettings)) return false; + return TransactionHashes.Length <= protocolSettings.MaxTransactionsPerBlock; + } + + public override void Serialize(BinaryWriter writer) + { + base.Serialize(writer); + writer.Write(Version); + writer.Write(PrevHash); + writer.Write(Timestamp); + writer.Write(Nonce); + writer.Write(TransactionHashes); + } + } +} diff --git a/src/Plugins/DBFTPlugin/Messages/PrepareResponse.cs b/src/Plugins/DBFTPlugin/Messages/PrepareResponse.cs new file mode 100644 index 0000000000..b4608ff9af --- /dev/null +++ b/src/Plugins/DBFTPlugin/Messages/PrepareResponse.cs @@ -0,0 +1,38 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// PrepareResponse.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using Neo.Plugins.DBFTPlugin.Types; +using System.IO; + +namespace Neo.Plugins.DBFTPlugin.Messages +{ + public class PrepareResponse : ConsensusMessage + { + public UInt256 PreparationHash; + + public override int Size => base.Size + PreparationHash.Size; + + public PrepareResponse() : base(ConsensusMessageType.PrepareResponse) { } + + public override void Deserialize(ref MemoryReader reader) + { + base.Deserialize(ref reader); + PreparationHash = reader.ReadSerializable(); + } + + public override void Serialize(BinaryWriter writer) + { + base.Serialize(writer); + writer.Write(PreparationHash); + } + } +} diff --git a/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.ChangeViewPayloadCompact.cs b/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.ChangeViewPayloadCompact.cs new file mode 100644 index 0000000000..2d7283cd22 --- /dev/null +++ b/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.ChangeViewPayloadCompact.cs @@ -0,0 +1,50 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RecoveryMessage.ChangeViewPayloadCompact.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using System; +using System.IO; + +namespace Neo.Plugins.DBFTPlugin.Messages +{ + partial class RecoveryMessage + { + public class ChangeViewPayloadCompact : ISerializable + { + public byte ValidatorIndex; + public byte OriginalViewNumber; + public ulong Timestamp; + public ReadOnlyMemory InvocationScript; + + int ISerializable.Size => + sizeof(byte) + //ValidatorIndex + sizeof(byte) + //OriginalViewNumber + sizeof(ulong) + //Timestamp + InvocationScript.GetVarSize(); //InvocationScript + + void ISerializable.Deserialize(ref MemoryReader reader) + { + ValidatorIndex = reader.ReadByte(); + OriginalViewNumber = reader.ReadByte(); + Timestamp = reader.ReadUInt64(); + InvocationScript = reader.ReadVarMemory(1024); + } + + void ISerializable.Serialize(BinaryWriter writer) + { + writer.Write(ValidatorIndex); + writer.Write(OriginalViewNumber); + writer.Write(Timestamp); + writer.WriteVarBytes(InvocationScript.Span); + } + } + } +} diff --git a/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.CommitPayloadCompact.cs b/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.CommitPayloadCompact.cs new file mode 100644 index 0000000000..6d3880f3c0 --- /dev/null +++ b/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.CommitPayloadCompact.cs @@ -0,0 +1,50 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RecoveryMessage.CommitPayloadCompact.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using System; +using System.IO; + +namespace Neo.Plugins.DBFTPlugin.Messages +{ + partial class RecoveryMessage + { + public class CommitPayloadCompact : ISerializable + { + public byte ViewNumber; + public byte ValidatorIndex; + public ReadOnlyMemory Signature; + public ReadOnlyMemory InvocationScript; + + int ISerializable.Size => + sizeof(byte) + //ViewNumber + sizeof(byte) + //ValidatorIndex + Signature.Length + //Signature + InvocationScript.GetVarSize(); //InvocationScript + + void ISerializable.Deserialize(ref MemoryReader reader) + { + ViewNumber = reader.ReadByte(); + ValidatorIndex = reader.ReadByte(); + Signature = reader.ReadMemory(64); + InvocationScript = reader.ReadVarMemory(1024); + } + + void ISerializable.Serialize(BinaryWriter writer) + { + writer.Write(ViewNumber); + writer.Write(ValidatorIndex); + writer.Write(Signature.Span); + writer.WriteVarBytes(InvocationScript.Span); + } + } + } +} diff --git a/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.PreparationPayloadCompact.cs b/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.PreparationPayloadCompact.cs new file mode 100644 index 0000000000..8fae1596ef --- /dev/null +++ b/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.PreparationPayloadCompact.cs @@ -0,0 +1,42 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RecoveryMessage.PreparationPayloadCompact.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using System; +using System.IO; + +namespace Neo.Plugins.DBFTPlugin.Messages +{ + partial class RecoveryMessage + { + public class PreparationPayloadCompact : ISerializable + { + public byte ValidatorIndex; + public ReadOnlyMemory InvocationScript; + + int ISerializable.Size => + sizeof(byte) + //ValidatorIndex + InvocationScript.GetVarSize(); //InvocationScript + + void ISerializable.Deserialize(ref MemoryReader reader) + { + ValidatorIndex = reader.ReadByte(); + InvocationScript = reader.ReadVarMemory(1024); + } + + void ISerializable.Serialize(BinaryWriter writer) + { + writer.Write(ValidatorIndex); + writer.WriteVarBytes(InvocationScript.Span); + } + } + } +} diff --git a/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.cs b/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.cs new file mode 100644 index 0000000000..2de33470ea --- /dev/null +++ b/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.cs @@ -0,0 +1,133 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RecoveryMessage.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using Neo.Network.P2P.Payloads; +using Neo.Plugins.DBFTPlugin.Consensus; +using Neo.Plugins.DBFTPlugin.Types; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Neo.Plugins.DBFTPlugin.Messages +{ + public partial class RecoveryMessage : ConsensusMessage + { + public Dictionary ChangeViewMessages; + public PrepareRequest PrepareRequestMessage; + /// The PreparationHash in case the PrepareRequest hasn't been received yet. + /// This can be null if the PrepareRequest information is present, since it can be derived in that case. + public UInt256 PreparationHash; + public Dictionary PreparationMessages; + public Dictionary CommitMessages; + + public override int Size => base.Size + + /* ChangeViewMessages */ ChangeViewMessages?.Values.GetVarSize() ?? 0 + + /* PrepareRequestMessage */ 1 + PrepareRequestMessage?.Size ?? 0 + + /* PreparationHash */ PreparationHash?.Size ?? 0 + + /* PreparationMessages */ PreparationMessages?.Values.GetVarSize() ?? 0 + + /* CommitMessages */ CommitMessages?.Values.GetVarSize() ?? 0; + + public RecoveryMessage() : base(ConsensusMessageType.RecoveryMessage) { } + + public override void Deserialize(ref MemoryReader reader) + { + base.Deserialize(ref reader); + ChangeViewMessages = reader.ReadSerializableArray(byte.MaxValue).ToDictionary(p => p.ValidatorIndex); + if (reader.ReadBoolean()) + { + PrepareRequestMessage = reader.ReadSerializable(); + } + else + { + int preparationHashSize = UInt256.Zero.Size; + if (preparationHashSize == (int)reader.ReadVarInt((ulong)preparationHashSize)) + PreparationHash = new UInt256(reader.ReadMemory(preparationHashSize).Span); + } + + PreparationMessages = reader.ReadSerializableArray(byte.MaxValue).ToDictionary(p => p.ValidatorIndex); + CommitMessages = reader.ReadSerializableArray(byte.MaxValue).ToDictionary(p => p.ValidatorIndex); + } + + public override bool Verify(ProtocolSettings protocolSettings) + { + if (!base.Verify(protocolSettings)) return false; + return (PrepareRequestMessage is null || PrepareRequestMessage.Verify(protocolSettings)) + && ChangeViewMessages.Values.All(p => p.ValidatorIndex < protocolSettings.ValidatorsCount) + && PreparationMessages.Values.All(p => p.ValidatorIndex < protocolSettings.ValidatorsCount) + && CommitMessages.Values.All(p => p.ValidatorIndex < protocolSettings.ValidatorsCount); + } + + internal ExtensiblePayload[] GetChangeViewPayloads(ConsensusContext context) + { + return ChangeViewMessages.Values.Select(p => context.CreatePayload(new ChangeView + { + BlockIndex = BlockIndex, + ValidatorIndex = p.ValidatorIndex, + ViewNumber = p.OriginalViewNumber, + Timestamp = p.Timestamp + }, p.InvocationScript)).ToArray(); + } + + internal ExtensiblePayload[] GetCommitPayloadsFromRecoveryMessage(ConsensusContext context) + { + return CommitMessages.Values.Select(p => context.CreatePayload(new Commit + { + BlockIndex = BlockIndex, + ValidatorIndex = p.ValidatorIndex, + ViewNumber = p.ViewNumber, + Signature = p.Signature + }, p.InvocationScript)).ToArray(); + } + + internal ExtensiblePayload GetPrepareRequestPayload(ConsensusContext context) + { + if (PrepareRequestMessage == null) return null; + if (!PreparationMessages.TryGetValue(context.Block.PrimaryIndex, out PreparationPayloadCompact compact)) + return null; + return context.CreatePayload(PrepareRequestMessage, compact.InvocationScript); + } + + internal ExtensiblePayload[] GetPrepareResponsePayloads(ConsensusContext context) + { + UInt256 preparationHash = PreparationHash ?? context.PreparationPayloads[context.Block.PrimaryIndex]?.Hash; + if (preparationHash is null) return Array.Empty(); + return PreparationMessages.Values.Where(p => p.ValidatorIndex != context.Block.PrimaryIndex).Select(p => context.CreatePayload(new PrepareResponse + { + BlockIndex = BlockIndex, + ValidatorIndex = p.ValidatorIndex, + ViewNumber = ViewNumber, + PreparationHash = preparationHash + }, p.InvocationScript)).ToArray(); + } + + public override void Serialize(BinaryWriter writer) + { + base.Serialize(writer); + writer.Write(ChangeViewMessages.Values.ToArray()); + bool hasPrepareRequestMessage = PrepareRequestMessage != null; + writer.Write(hasPrepareRequestMessage); + if (hasPrepareRequestMessage) + writer.Write(PrepareRequestMessage); + else + { + if (PreparationHash == null) + writer.WriteVarInt(0); + else + writer.WriteVarBytes(PreparationHash.ToArray()); + } + + writer.Write(PreparationMessages.Values.ToArray()); + writer.Write(CommitMessages.Values.ToArray()); + } + } +} diff --git a/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryRequest.cs b/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryRequest.cs new file mode 100644 index 0000000000..2872d28e26 --- /dev/null +++ b/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryRequest.cs @@ -0,0 +1,44 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RecoveryRequest.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using Neo.Plugins.DBFTPlugin.Types; +using System.IO; + +namespace Neo.Plugins.DBFTPlugin.Messages +{ + public class RecoveryRequest : ConsensusMessage + { + /// + /// Timestamp of when the ChangeView message was created. This allows receiving nodes to ensure + /// they only respond once to a specific RecoveryRequest request. + /// In this sense, it prevents replay of the RecoveryRequest message from the repeatedly broadcast of Recovery's messages. + /// + public ulong Timestamp; + + public override int Size => base.Size + + sizeof(ulong); //Timestamp + + public RecoveryRequest() : base(ConsensusMessageType.RecoveryRequest) { } + + public override void Deserialize(ref MemoryReader reader) + { + base.Deserialize(ref reader); + Timestamp = reader.ReadUInt64(); + } + + public override void Serialize(BinaryWriter writer) + { + base.Serialize(writer); + writer.Write(Timestamp); + } + } +} diff --git a/src/Plugins/DBFTPlugin/Settings.cs b/src/Plugins/DBFTPlugin/Settings.cs new file mode 100644 index 0000000000..28ad21f37a --- /dev/null +++ b/src/Plugins/DBFTPlugin/Settings.cs @@ -0,0 +1,35 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Settings.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.Extensions.Configuration; + +namespace Neo.Plugins.DBFTPlugin +{ + public class Settings + { + public string RecoveryLogs { get; } + public bool IgnoreRecoveryLogs { get; } + public bool AutoStart { get; } + public uint Network { get; } + public uint MaxBlockSize { get; } + public long MaxBlockSystemFee { get; } + + public Settings(IConfigurationSection section) + { + RecoveryLogs = section.GetValue("RecoveryLogs", "ConsensusState"); + IgnoreRecoveryLogs = section.GetValue("IgnoreRecoveryLogs", false); + AutoStart = section.GetValue("AutoStart", false); + Network = section.GetValue("Network", 5195086u); + MaxBlockSize = section.GetValue("MaxBlockSize", 262144u); + MaxBlockSystemFee = section.GetValue("MaxBlockSystemFee", 150000000000L); + } + } +} diff --git a/src/Plugins/DBFTPlugin/Types/ChangeViewReason.cs b/src/Plugins/DBFTPlugin/Types/ChangeViewReason.cs new file mode 100644 index 0000000000..5d9e55ffeb --- /dev/null +++ b/src/Plugins/DBFTPlugin/Types/ChangeViewReason.cs @@ -0,0 +1,23 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ChangeViewReason.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.Plugins.DBFTPlugin.Types +{ + public enum ChangeViewReason : byte + { + Timeout = 0x0, + ChangeAgreement = 0x1, + TxNotFound = 0x2, + TxRejectedByPolicy = 0x3, + TxInvalid = 0x4, + BlockRejectedByPolicy = 0x5 + } +} diff --git a/src/Plugins/DBFTPlugin/Types/ConsensusMessageType.cs b/src/Plugins/DBFTPlugin/Types/ConsensusMessageType.cs new file mode 100644 index 0000000000..81636e94c1 --- /dev/null +++ b/src/Plugins/DBFTPlugin/Types/ConsensusMessageType.cs @@ -0,0 +1,25 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ConsensusMessageType.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.Plugins.DBFTPlugin.Types +{ + public enum ConsensusMessageType : byte + { + ChangeView = 0x00, + + PrepareRequest = 0x20, + PrepareResponse = 0x21, + Commit = 0x30, + + RecoveryRequest = 0x40, + RecoveryMessage = 0x41, + } +} diff --git a/src/Plugins/Directory.Build.props b/src/Plugins/Directory.Build.props new file mode 100644 index 0000000000..72e96f0300 --- /dev/null +++ b/src/Plugins/Directory.Build.props @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/Plugins/LevelDBStore/IO/Data/LevelDB/DB.cs b/src/Plugins/LevelDBStore/IO/Data/LevelDB/DB.cs new file mode 100644 index 0000000000..60e0e24e5a --- /dev/null +++ b/src/Plugins/LevelDBStore/IO/Data/LevelDB/DB.cs @@ -0,0 +1,114 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// DB.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.IO; + +namespace Neo.IO.Data.LevelDB +{ + public class DB : IDisposable + { + private IntPtr handle; + + /// + /// Return true if haven't got valid handle + /// + public bool IsDisposed => handle == IntPtr.Zero; + + private DB(IntPtr handle) + { + this.handle = handle; + } + + public void Dispose() + { + if (handle != IntPtr.Zero) + { + Native.leveldb_close(handle); + handle = IntPtr.Zero; + } + } + + public void Delete(WriteOptions options, byte[] key) + { + Native.leveldb_delete(handle, options.handle, key, (UIntPtr)key.Length, out IntPtr error); + NativeHelper.CheckError(error); + } + + public byte[] Get(ReadOptions options, byte[] key) + { + IntPtr value = Native.leveldb_get(handle, options.handle, key, (UIntPtr)key.Length, out UIntPtr length, out IntPtr error); + try + { + NativeHelper.CheckError(error); + return value.ToByteArray(length); + } + finally + { + if (value != IntPtr.Zero) Native.leveldb_free(value); + } + } + + public bool Contains(ReadOptions options, byte[] key) + { + IntPtr value = Native.leveldb_get(handle, options.handle, key, (UIntPtr)key.Length, out _, out IntPtr error); + NativeHelper.CheckError(error); + + if (value != IntPtr.Zero) + { + Native.leveldb_free(value); + return true; + } + + return false; + } + + public Snapshot GetSnapshot() + { + return new Snapshot(handle); + } + + public Iterator NewIterator(ReadOptions options) + { + return new Iterator(Native.leveldb_create_iterator(handle, options.handle)); + } + + public static DB Open(string name) + { + return Open(name, Options.Default); + } + + public static DB Open(string name, Options options) + { + IntPtr handle = Native.leveldb_open(options.handle, Path.GetFullPath(name), out IntPtr error); + NativeHelper.CheckError(error); + return new DB(handle); + } + + public void Put(WriteOptions options, byte[] key, byte[] value) + { + Native.leveldb_put(handle, options.handle, key, (UIntPtr)key.Length, value, (UIntPtr)value.Length, out IntPtr error); + NativeHelper.CheckError(error); + } + + public static void Repair(string name, Options options) + { + Native.leveldb_repair_db(options.handle, Path.GetFullPath(name), out IntPtr error); + NativeHelper.CheckError(error); + } + + public void Write(WriteOptions options, WriteBatch write_batch) + { + Native.leveldb_write(handle, options.handle, write_batch.handle, out IntPtr error); + NativeHelper.CheckError(error); + } + } +} diff --git a/src/Plugins/LevelDBStore/IO/Data/LevelDB/Helper.cs b/src/Plugins/LevelDBStore/IO/Data/LevelDB/Helper.cs new file mode 100644 index 0000000000..2ac3a0005f --- /dev/null +++ b/src/Plugins/LevelDBStore/IO/Data/LevelDB/Helper.cs @@ -0,0 +1,63 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Helper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Persistence; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +namespace Neo.IO.Data.LevelDB +{ + public static class Helper + { + public static IEnumerable Seek(this DB db, ReadOptions options, byte[] prefix, SeekDirection direction, Func resultSelector) + { + using Iterator it = db.NewIterator(options); + if (direction == SeekDirection.Forward) + { + for (it.Seek(prefix); it.Valid(); it.Next()) + yield return resultSelector(it.Key(), it.Value()); + } + else + { + // SeekForPrev + + it.Seek(prefix); + if (!it.Valid()) + it.SeekToLast(); + else if (it.Key().AsSpan().SequenceCompareTo(prefix) > 0) + it.Prev(); + + for (; it.Valid(); it.Prev()) + yield return resultSelector(it.Key(), it.Value()); + } + } + + public static IEnumerable FindRange(this DB db, ReadOptions options, byte[] startKey, byte[] endKey, Func resultSelector) + { + using Iterator it = db.NewIterator(options); + for (it.Seek(startKey); it.Valid(); it.Next()) + { + byte[] key = it.Key(); + if (key.AsSpan().SequenceCompareTo(endKey) > 0) break; + yield return resultSelector(key, it.Value()); + } + } + + internal static byte[] ToByteArray(this IntPtr data, UIntPtr length) + { + if (data == IntPtr.Zero) return null; + byte[] buffer = new byte[(int)length]; + Marshal.Copy(data, buffer, 0, (int)length); + return buffer; + } + } +} diff --git a/src/Plugins/LevelDBStore/IO/Data/LevelDB/Iterator.cs b/src/Plugins/LevelDBStore/IO/Data/LevelDB/Iterator.cs new file mode 100644 index 0000000000..6c025380fb --- /dev/null +++ b/src/Plugins/LevelDBStore/IO/Data/LevelDB/Iterator.cs @@ -0,0 +1,86 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Iterator.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; + +namespace Neo.IO.Data.LevelDB +{ + public class Iterator : IDisposable + { + private IntPtr handle; + + internal Iterator(IntPtr handle) + { + this.handle = handle; + } + + private void CheckError() + { + Native.leveldb_iter_get_error(handle, out IntPtr error); + NativeHelper.CheckError(error); + } + + public void Dispose() + { + if (handle != IntPtr.Zero) + { + Native.leveldb_iter_destroy(handle); + handle = IntPtr.Zero; + } + } + + public byte[] Key() + { + IntPtr key = Native.leveldb_iter_key(handle, out UIntPtr length); + CheckError(); + return key.ToByteArray(length); + } + + public void Next() + { + Native.leveldb_iter_next(handle); + CheckError(); + } + + public void Prev() + { + Native.leveldb_iter_prev(handle); + CheckError(); + } + + public void Seek(byte[] target) + { + Native.leveldb_iter_seek(handle, target, (UIntPtr)target.Length); + } + + public void SeekToFirst() + { + Native.leveldb_iter_seek_to_first(handle); + } + + public void SeekToLast() + { + Native.leveldb_iter_seek_to_last(handle); + } + + public bool Valid() + { + return Native.leveldb_iter_valid(handle); + } + + public byte[] Value() + { + IntPtr value = Native.leveldb_iter_value(handle, out UIntPtr length); + CheckError(); + return value.ToByteArray(length); + } + } +} diff --git a/src/Plugins/LevelDBStore/IO/Data/LevelDB/LevelDBException.cs b/src/Plugins/LevelDBStore/IO/Data/LevelDB/LevelDBException.cs new file mode 100644 index 0000000000..c9cca42070 --- /dev/null +++ b/src/Plugins/LevelDBStore/IO/Data/LevelDB/LevelDBException.cs @@ -0,0 +1,23 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// LevelDBException.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Data.Common; + +namespace Neo.IO.Data.LevelDB +{ + public class LevelDBException : DbException + { + internal LevelDBException(string message) + : base(message) + { + } + } +} diff --git a/src/Plugins/LevelDBStore/IO/Data/LevelDB/Native.cs b/src/Plugins/LevelDBStore/IO/Data/LevelDB/Native.cs new file mode 100644 index 0000000000..acf8fa82b9 --- /dev/null +++ b/src/Plugins/LevelDBStore/IO/Data/LevelDB/Native.cs @@ -0,0 +1,264 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Native.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Neo.IO.Data.LevelDB +{ + public enum CompressionType : byte + { + kNoCompression = 0x0, + kSnappyCompression = 0x1 + } + + public static class Native + { + #region Logger + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr leveldb_logger_create(IntPtr /* Action */ logger); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_logger_destroy(IntPtr /* logger*/ option); + #endregion + + #region DB + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr leveldb_open(IntPtr /* Options*/ options, string name, out IntPtr error); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_close(IntPtr /*DB */ db); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_put(IntPtr /* DB */ db, IntPtr /* WriteOptions*/ options, byte[] key, UIntPtr keylen, byte[] val, UIntPtr vallen, out IntPtr errptr); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_delete(IntPtr /* DB */ db, IntPtr /* WriteOptions*/ options, byte[] key, UIntPtr keylen, out IntPtr errptr); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_write(IntPtr /* DB */ db, IntPtr /* WriteOptions*/ options, IntPtr /* WriteBatch */ batch, out IntPtr errptr); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr leveldb_get(IntPtr /* DB */ db, IntPtr /* ReadOptions*/ options, byte[] key, UIntPtr keylen, out UIntPtr vallen, out IntPtr errptr); + + //[DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + //static extern void leveldb_approximate_sizes(IntPtr /* DB */ db, int num_ranges, byte[] range_start_key, long range_start_key_len, byte[] range_limit_key, long range_limit_key_len, out long sizes); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr leveldb_create_iterator(IntPtr /* DB */ db, IntPtr /* ReadOption */ options); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr leveldb_create_snapshot(IntPtr /* DB */ db); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_release_snapshot(IntPtr /* DB */ db, IntPtr /* SnapShot*/ snapshot); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr leveldb_property_value(IntPtr /* DB */ db, string propname); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_repair_db(IntPtr /* Options*/ options, string name, out IntPtr error); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_destroy_db(IntPtr /* Options*/ options, string name, out IntPtr error); + + #region extensions + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_free(IntPtr /* void */ ptr); + + #endregion + + + #endregion + + #region Env + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr leveldb_create_default_env(); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_env_destroy(IntPtr /*Env*/ cache); + #endregion + + #region Iterator + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_iter_destroy(IntPtr /*Iterator*/ iterator); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.U1)] + public static extern bool leveldb_iter_valid(IntPtr /*Iterator*/ iterator); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_iter_seek_to_first(IntPtr /*Iterator*/ iterator); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_iter_seek_to_last(IntPtr /*Iterator*/ iterator); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_iter_seek(IntPtr /*Iterator*/ iterator, byte[] key, UIntPtr length); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_iter_next(IntPtr /*Iterator*/ iterator); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_iter_prev(IntPtr /*Iterator*/ iterator); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr leveldb_iter_key(IntPtr /*Iterator*/ iterator, out UIntPtr length); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr leveldb_iter_value(IntPtr /*Iterator*/ iterator, out UIntPtr length); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_iter_get_error(IntPtr /*Iterator*/ iterator, out IntPtr error); + #endregion + + #region Options + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr leveldb_options_create(); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_options_destroy(IntPtr /*Options*/ options); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_options_set_create_if_missing(IntPtr /*Options*/ options, [MarshalAs(UnmanagedType.U1)] bool o); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_options_set_error_if_exists(IntPtr /*Options*/ options, [MarshalAs(UnmanagedType.U1)] bool o); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_options_set_info_log(IntPtr /*Options*/ options, IntPtr /* Logger */ logger); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_options_set_paranoid_checks(IntPtr /*Options*/ options, [MarshalAs(UnmanagedType.U1)] bool o); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_options_set_env(IntPtr /*Options*/ options, IntPtr /*Env*/ env); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_options_set_write_buffer_size(IntPtr /*Options*/ options, UIntPtr size); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_options_set_max_open_files(IntPtr /*Options*/ options, int max); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_options_set_cache(IntPtr /*Options*/ options, IntPtr /*Cache*/ cache); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_options_set_block_size(IntPtr /*Options*/ options, UIntPtr size); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_options_set_block_restart_interval(IntPtr /*Options*/ options, int interval); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_options_set_compression(IntPtr /*Options*/ options, CompressionType level); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_options_set_comparator(IntPtr /*Options*/ options, IntPtr /*Comparator*/ comparer); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_options_set_filter_policy(IntPtr /*Options*/ options, IntPtr /*FilterPolicy*/ policy); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr leveldb_filterpolicy_create_bloom(int bits_per_key); + #endregion + + #region ReadOptions + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr leveldb_readoptions_create(); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_readoptions_destroy(IntPtr /*ReadOptions*/ options); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_readoptions_set_verify_checksums(IntPtr /*ReadOptions*/ options, [MarshalAs(UnmanagedType.U1)] bool o); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_readoptions_set_fill_cache(IntPtr /*ReadOptions*/ options, [MarshalAs(UnmanagedType.U1)] bool o); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_readoptions_set_snapshot(IntPtr /*ReadOptions*/ options, IntPtr /*SnapShot*/ snapshot); + #endregion + + #region WriteBatch + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr leveldb_writebatch_create(); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_writebatch_destroy(IntPtr /* WriteBatch */ batch); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_writebatch_clear(IntPtr /* WriteBatch */ batch); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_writebatch_put(IntPtr /* WriteBatch */ batch, byte[] key, UIntPtr keylen, byte[] val, UIntPtr vallen); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_writebatch_delete(IntPtr /* WriteBatch */ batch, byte[] key, UIntPtr keylen); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_writebatch_iterate(IntPtr /* WriteBatch */ batch, object state, Action put, Action deleted); + #endregion + + #region WriteOptions + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr leveldb_writeoptions_create(); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_writeoptions_destroy(IntPtr /*WriteOptions*/ options); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_writeoptions_set_sync(IntPtr /*WriteOptions*/ options, [MarshalAs(UnmanagedType.U1)] bool o); + #endregion + + #region Cache + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr leveldb_cache_create_lru(int capacity); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_cache_destroy(IntPtr /*Cache*/ cache); + #endregion + + #region Comparator + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr /* leveldb_comparator_t* */ + leveldb_comparator_create( + IntPtr /* void* */ state, + IntPtr /* void (*)(void*) */ destructor, + IntPtr + /* int (*compare)(void*, + const char* a, size_t alen, + const char* b, size_t blen) */ + compare, + IntPtr /* const char* (*)(void*) */ name); + + [DllImport("libleveldb", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)] + public static extern void leveldb_comparator_destroy(IntPtr /* leveldb_comparator_t* */ cmp); + + #endregion + } + + internal static class NativeHelper + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CheckError(IntPtr error) + { + if (error != IntPtr.Zero) + { + string message = Marshal.PtrToStringAnsi(error); + Native.leveldb_free(error); + throw new LevelDBException(message); + } + } + } +} diff --git a/src/Plugins/LevelDBStore/IO/Data/LevelDB/Options.cs b/src/Plugins/LevelDBStore/IO/Data/LevelDB/Options.cs new file mode 100644 index 0000000000..989987eeed --- /dev/null +++ b/src/Plugins/LevelDBStore/IO/Data/LevelDB/Options.cs @@ -0,0 +1,98 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Options.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; + +namespace Neo.IO.Data.LevelDB +{ + public class Options + { + public static readonly Options Default = new Options(); + internal readonly IntPtr handle = Native.leveldb_options_create(); + + public bool CreateIfMissing + { + set + { + Native.leveldb_options_set_create_if_missing(handle, value); + } + } + + public bool ErrorIfExists + { + set + { + Native.leveldb_options_set_error_if_exists(handle, value); + } + } + + public bool ParanoidChecks + { + set + { + Native.leveldb_options_set_paranoid_checks(handle, value); + } + } + + public int WriteBufferSize + { + set + { + Native.leveldb_options_set_write_buffer_size(handle, (UIntPtr)value); + } + } + + public int MaxOpenFiles + { + set + { + Native.leveldb_options_set_max_open_files(handle, value); + } + } + + public int BlockSize + { + set + { + Native.leveldb_options_set_block_size(handle, (UIntPtr)value); + } + } + + public int BlockRestartInterval + { + set + { + Native.leveldb_options_set_block_restart_interval(handle, value); + } + } + + public CompressionType Compression + { + set + { + Native.leveldb_options_set_compression(handle, value); + } + } + + public IntPtr FilterPolicy + { + set + { + Native.leveldb_options_set_filter_policy(handle, value); + } + } + + ~Options() + { + Native.leveldb_options_destroy(handle); + } + } +} diff --git a/src/Plugins/LevelDBStore/IO/Data/LevelDB/ReadOptions.cs b/src/Plugins/LevelDBStore/IO/Data/LevelDB/ReadOptions.cs new file mode 100644 index 0000000000..727ae9f02a --- /dev/null +++ b/src/Plugins/LevelDBStore/IO/Data/LevelDB/ReadOptions.cs @@ -0,0 +1,50 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ReadOptions.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; + +namespace Neo.IO.Data.LevelDB +{ + public class ReadOptions + { + public static readonly ReadOptions Default = new ReadOptions(); + internal readonly IntPtr handle = Native.leveldb_readoptions_create(); + + public bool VerifyChecksums + { + set + { + Native.leveldb_readoptions_set_verify_checksums(handle, value); + } + } + + public bool FillCache + { + set + { + Native.leveldb_readoptions_set_fill_cache(handle, value); + } + } + + public Snapshot Snapshot + { + set + { + Native.leveldb_readoptions_set_snapshot(handle, value.handle); + } + } + + ~ReadOptions() + { + Native.leveldb_readoptions_destroy(handle); + } + } +} diff --git a/src/Plugins/LevelDBStore/IO/Data/LevelDB/Snapshot.cs b/src/Plugins/LevelDBStore/IO/Data/LevelDB/Snapshot.cs new file mode 100644 index 0000000000..14280fbc8f --- /dev/null +++ b/src/Plugins/LevelDBStore/IO/Data/LevelDB/Snapshot.cs @@ -0,0 +1,35 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Snapshot.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; + +namespace Neo.IO.Data.LevelDB +{ + public class Snapshot : IDisposable + { + internal IntPtr db, handle; + + internal Snapshot(IntPtr db) + { + this.db = db; + handle = Native.leveldb_create_snapshot(db); + } + + public void Dispose() + { + if (handle != IntPtr.Zero) + { + Native.leveldb_release_snapshot(db, handle); + handle = IntPtr.Zero; + } + } + } +} diff --git a/src/Plugins/LevelDBStore/IO/Data/LevelDB/WriteBatch.cs b/src/Plugins/LevelDBStore/IO/Data/LevelDB/WriteBatch.cs new file mode 100644 index 0000000000..ad82dad450 --- /dev/null +++ b/src/Plugins/LevelDBStore/IO/Data/LevelDB/WriteBatch.cs @@ -0,0 +1,40 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// WriteBatch.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; + +namespace Neo.IO.Data.LevelDB +{ + public class WriteBatch + { + internal readonly IntPtr handle = Native.leveldb_writebatch_create(); + + ~WriteBatch() + { + Native.leveldb_writebatch_destroy(handle); + } + + public void Clear() + { + Native.leveldb_writebatch_clear(handle); + } + + public void Delete(byte[] key) + { + Native.leveldb_writebatch_delete(handle, key, (UIntPtr)key.Length); + } + + public void Put(byte[] key, byte[] value) + { + Native.leveldb_writebatch_put(handle, key, (UIntPtr)key.Length, value, (UIntPtr)value.Length); + } + } +} diff --git a/src/Plugins/LevelDBStore/IO/Data/LevelDB/WriteOptions.cs b/src/Plugins/LevelDBStore/IO/Data/LevelDB/WriteOptions.cs new file mode 100644 index 0000000000..48915ba480 --- /dev/null +++ b/src/Plugins/LevelDBStore/IO/Data/LevelDB/WriteOptions.cs @@ -0,0 +1,36 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// WriteOptions.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; + +namespace Neo.IO.Data.LevelDB +{ + public class WriteOptions + { + public static readonly WriteOptions Default = new WriteOptions(); + public static readonly WriteOptions SyncWrite = new WriteOptions { Sync = true }; + + internal readonly IntPtr handle = Native.leveldb_writeoptions_create(); + + public bool Sync + { + set + { + Native.leveldb_writeoptions_set_sync(handle, value); + } + } + + ~WriteOptions() + { + Native.leveldb_writeoptions_destroy(handle); + } + } +} diff --git a/src/Plugins/LevelDBStore/LevelDBStore.csproj b/src/Plugins/LevelDBStore/LevelDBStore.csproj new file mode 100644 index 0000000000..23cf469640 --- /dev/null +++ b/src/Plugins/LevelDBStore/LevelDBStore.csproj @@ -0,0 +1,11 @@ + + + + net8.0 + Neo.Plugins.Storage.LevelDBStore + Neo.Plugins.Storage + true + ../../../bin/$(PackageId) + + + diff --git a/src/Plugins/LevelDBStore/Plugins/Storage/LevelDBStore.cs b/src/Plugins/LevelDBStore/Plugins/Storage/LevelDBStore.cs new file mode 100644 index 0000000000..9c676e8a7f --- /dev/null +++ b/src/Plugins/LevelDBStore/Plugins/Storage/LevelDBStore.cs @@ -0,0 +1,35 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// LevelDBStore.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO.Data.LevelDB; +using Neo.Persistence; +using System; +using System.Linq; + +namespace Neo.Plugins.Storage +{ + public class LevelDBStore : Plugin, IStoreProvider + { + public override string Description => "Uses LevelDB to store the blockchain data"; + + public LevelDBStore() + { + StoreFactory.RegisterProvider(this); + } + + public IStore GetStore(string path) + { + if (Environment.CommandLine.Split(' ').Any(p => p == "/repair" || p == "--repair")) + DB.Repair(path, Options.Default); + return new Store(path); + } + } +} diff --git a/src/Plugins/LevelDBStore/Plugins/Storage/Snapshot.cs b/src/Plugins/LevelDBStore/Plugins/Storage/Snapshot.cs new file mode 100644 index 0000000000..0b0a63b885 --- /dev/null +++ b/src/Plugins/LevelDBStore/Plugins/Storage/Snapshot.cs @@ -0,0 +1,69 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Snapshot.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO.Data.LevelDB; +using Neo.Persistence; +using System.Collections.Generic; +using LSnapshot = Neo.IO.Data.LevelDB.Snapshot; + +namespace Neo.Plugins.Storage +{ + internal class Snapshot : ISnapshot + { + private readonly DB db; + private readonly LSnapshot snapshot; + private readonly ReadOptions options; + private readonly WriteBatch batch; + + public Snapshot(DB db) + { + this.db = db; + snapshot = db.GetSnapshot(); + options = new ReadOptions { FillCache = false, Snapshot = snapshot }; + batch = new WriteBatch(); + } + + public void Commit() + { + db.Write(WriteOptions.Default, batch); + } + + public void Delete(byte[] key) + { + batch.Delete(key); + } + + public void Dispose() + { + snapshot.Dispose(); + } + + public IEnumerable<(byte[] Key, byte[] Value)> Seek(byte[] prefix, SeekDirection direction = SeekDirection.Forward) + { + return db.Seek(options, prefix, direction, (k, v) => (k, v)); + } + + public void Put(byte[] key, byte[] value) + { + batch.Put(key, value); + } + + public bool Contains(byte[] key) + { + return db.Contains(options, key); + } + + public byte[] TryGet(byte[] key) + { + return db.Get(options, key); + } + } +} diff --git a/src/Plugins/LevelDBStore/Plugins/Storage/Store.cs b/src/Plugins/LevelDBStore/Plugins/Storage/Store.cs new file mode 100644 index 0000000000..27b12a8b64 --- /dev/null +++ b/src/Plugins/LevelDBStore/Plugins/Storage/Store.cs @@ -0,0 +1,67 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Store.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO.Data.LevelDB; +using Neo.Persistence; +using System.Collections.Generic; + +namespace Neo.Plugins.Storage +{ + internal class Store : IStore + { + private readonly DB db; + + public Store(string path) + { + db = DB.Open(path, new Options { CreateIfMissing = true, FilterPolicy = Native.leveldb_filterpolicy_create_bloom(15) }); + } + + public void Delete(byte[] key) + { + db.Delete(WriteOptions.Default, key); + } + + public void Dispose() + { + db.Dispose(); + } + + public IEnumerable<(byte[], byte[])> Seek(byte[] prefix, SeekDirection direction = SeekDirection.Forward) + { + return db.Seek(ReadOptions.Default, prefix, direction, (k, v) => (k, v)); + } + + public ISnapshot GetSnapshot() + { + return new Snapshot(db); + } + + public void Put(byte[] key, byte[] value) + { + db.Put(WriteOptions.Default, key, value); + } + + public void PutSync(byte[] key, byte[] value) + { + db.Put(WriteOptions.SyncWrite, key, value); + } + + public bool Contains(byte[] key) + { + return db.Contains(ReadOptions.Default, key); + } + + public byte[] TryGet(byte[] key) + { + return db.Get(ReadOptions.Default, key); + } + } +} diff --git a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Cache.cs b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Cache.cs new file mode 100644 index 0000000000..d8baef8529 --- /dev/null +++ b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Cache.cs @@ -0,0 +1,126 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Cache.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using Neo.Persistence; +using System.Collections.Generic; +using System.IO; + +namespace Neo.Cryptography.MPTTrie +{ + public class Cache + { + private enum TrackState : byte + { + None, + Added, + Changed, + Deleted + } + + private class Trackable + { + public Node Node; + public TrackState State; + } + + private readonly ISnapshot store; + private readonly byte prefix; + private readonly Dictionary cache = new Dictionary(); + + public Cache(ISnapshot store, byte prefix) + { + this.store = store; + this.prefix = prefix; + } + + private byte[] Key(UInt256 hash) + { + byte[] buffer = new byte[UInt256.Length + 1]; + using (MemoryStream ms = new MemoryStream(buffer, true)) + using (BinaryWriter writer = new BinaryWriter(ms)) + { + writer.Write(prefix); + hash.Serialize(writer); + } + return buffer; + } + + public Node Resolve(UInt256 hash) + { + if (cache.TryGetValue(hash, out Trackable t)) + { + return t.Node?.Clone(); + } + var n = store.TryGet(Key(hash))?.AsSerializable(); + cache.Add(hash, new Trackable + { + Node = n, + State = TrackState.None, + }); + return n?.Clone(); + } + + public void PutNode(Node np) + { + var n = Resolve(np.Hash); + if (n is null) + { + np.Reference = 1; + cache[np.Hash] = new Trackable + { + Node = np.Clone(), + State = TrackState.Added, + }; + return; + } + var entry = cache[np.Hash]; + entry.Node.Reference++; + entry.State = TrackState.Changed; + } + + public void DeleteNode(UInt256 hash) + { + var n = Resolve(hash); + if (n is null) return; + if (1 < n.Reference) + { + var entry = cache[hash]; + entry.Node.Reference--; + entry.State = TrackState.Changed; + return; + } + cache[hash] = new Trackable + { + Node = null, + State = TrackState.Deleted, + }; + } + + public void Commit() + { + foreach (var item in cache) + { + switch (item.Value.State) + { + case TrackState.Added: + case TrackState.Changed: + store.Put(Key(item.Key), item.Value.Node.ToArray()); + break; + case TrackState.Deleted: + store.Delete(Key(item.Key)); + break; + } + } + cache.Clear(); + } + } +} diff --git a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Helper.cs b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Helper.cs new file mode 100644 index 0000000000..5c93afd659 --- /dev/null +++ b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Helper.cs @@ -0,0 +1,28 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Helper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; + +namespace Neo.Cryptography.MPTTrie +{ + public static class Helper + { + public static int CompareTo(this ReadOnlySpan arr1, ReadOnlySpan arr2) + { + for (int i = 0; i < arr1.Length && i < arr2.Length; i++) + { + var r = arr1[i].CompareTo(arr2[i]); + if (r != 0) return r; + } + return arr2.Length < arr1.Length ? 1 : arr2.Length == arr1.Length ? 0 : -1; + } + } +} diff --git a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.Branch.cs b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.Branch.cs new file mode 100644 index 0000000000..c8ff04dfc6 --- /dev/null +++ b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.Branch.cs @@ -0,0 +1,69 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Node.Branch.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using System.IO; + +namespace Neo.Cryptography.MPTTrie +{ + partial class Node + { + public const int BranchChildCount = 17; + public Node[] Children; + + public static Node NewBranch() + { + var n = new Node + { + type = NodeType.BranchNode, + Reference = 1, + Children = new Node[BranchChildCount], + }; + for (int i = 0; i < BranchChildCount; i++) + { + n.Children[i] = new Node(); + } + return n; + } + + protected int BranchSize + { + get + { + int size = 0; + for (int i = 0; i < BranchChildCount; i++) + { + size += Children[i].SizeAsChild; + } + return size; + } + } + + private void SerializeBranch(BinaryWriter writer) + { + for (int i = 0; i < BranchChildCount; i++) + { + Children[i].SerializeAsChild(writer); + } + } + + private void DeserializeBranch(ref MemoryReader reader) + { + Children = new Node[BranchChildCount]; + for (int i = 0; i < BranchChildCount; i++) + { + var n = new Node(); + n.Deserialize(ref reader); + Children[i] = n; + } + } + } +} diff --git a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.Extension.cs b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.Extension.cs new file mode 100644 index 0000000000..510db49250 --- /dev/null +++ b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.Extension.cs @@ -0,0 +1,55 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Node.Extension.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using Neo.SmartContract; +using System; +using System.IO; + +namespace Neo.Cryptography.MPTTrie +{ + partial class Node + { + public const int MaxKeyLength = (ApplicationEngine.MaxStorageKeySize + sizeof(int)) * 2; + public ReadOnlyMemory Key; + public Node Next; + + public static Node NewExtension(byte[] key, Node next) + { + if (key is null || next is null) throw new ArgumentNullException(nameof(NewExtension)); + if (key.Length == 0) throw new InvalidOperationException(nameof(NewExtension)); + var n = new Node + { + type = NodeType.ExtensionNode, + Key = key, + Next = next, + Reference = 1, + }; + return n; + } + + protected int ExtensionSize => Key.GetVarSize() + Next.SizeAsChild; + + private void SerializeExtension(BinaryWriter writer) + { + writer.WriteVarBytes(Key.Span); + Next.SerializeAsChild(writer); + } + + private void DeserializeExtension(ref MemoryReader reader) + { + Key = reader.ReadVarMemory(); + var n = new Node(); + n.Deserialize(ref reader); + Next = n; + } + } +} diff --git a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.Hash.cs b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.Hash.cs new file mode 100644 index 0000000000..e0190dd146 --- /dev/null +++ b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.Hash.cs @@ -0,0 +1,43 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Node.Hash.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using System; +using System.IO; + +namespace Neo.Cryptography.MPTTrie +{ + partial class Node + { + public static Node NewHash(UInt256 hash) + { + if (hash is null) throw new ArgumentNullException(nameof(NewHash)); + var n = new Node + { + type = NodeType.HashNode, + hash = hash, + }; + return n; + } + + protected int HashSize => hash.Size; + + private void SerializeHash(BinaryWriter writer) + { + writer.Write(hash); + } + + private void DeserializeHash(ref MemoryReader reader) + { + hash = reader.ReadSerializable(); + } + } +} diff --git a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.Leaf.cs b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.Leaf.cs new file mode 100644 index 0000000000..024a07f8c8 --- /dev/null +++ b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.Leaf.cs @@ -0,0 +1,48 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Node.Leaf.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using Neo.SmartContract; +using System; +using System.IO; + +namespace Neo.Cryptography.MPTTrie +{ + partial class Node + { + public const int MaxValueLength = 3 + ApplicationEngine.MaxStorageValueSize + sizeof(bool); + public ReadOnlyMemory Value; + + public static Node NewLeaf(byte[] value) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + var n = new Node + { + type = NodeType.LeafNode, + Value = value, + Reference = 1, + }; + return n; + } + + protected int LeafSize => Value.GetVarSize(); + + private void SerializeLeaf(BinaryWriter writer) + { + writer.WriteVarBytes(Value.Span); + } + + private void DeserializeLeaf(ref MemoryReader reader) + { + Value = reader.ReadVarMemory(); + } + } +} diff --git a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.cs b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.cs new file mode 100644 index 0000000000..ef45548645 --- /dev/null +++ b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.cs @@ -0,0 +1,230 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Node.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using System; +using System.IO; + +namespace Neo.Cryptography.MPTTrie +{ + public partial class Node : ISerializable + { + private NodeType type; + private UInt256 hash; + public int Reference; + public UInt256 Hash => hash ??= new UInt256(Crypto.Hash256(ToArrayWithoutReference())); + public NodeType Type => type; + public bool IsEmpty => type == NodeType.Empty; + public int Size + { + get + { + int size = sizeof(NodeType); + switch (type) + { + case NodeType.BranchNode: + return size + BranchSize + IO.Helper.GetVarSize(Reference); + case NodeType.ExtensionNode: + return size + ExtensionSize + IO.Helper.GetVarSize(Reference); + case NodeType.LeafNode: + return size + LeafSize + IO.Helper.GetVarSize(Reference); + case NodeType.HashNode: + return size + HashSize; + case NodeType.Empty: + return size; + default: + throw new InvalidOperationException($"{nameof(Node)} Cannt get size, unsupport type"); + }; + } + } + + public Node() + { + type = NodeType.Empty; + } + + public void SetDirty() + { + hash = null; + } + + public int SizeAsChild + { + get + { + switch (type) + { + case NodeType.BranchNode: + case NodeType.ExtensionNode: + case NodeType.LeafNode: + return NewHash(Hash).Size; + case NodeType.HashNode: + case NodeType.Empty: + return Size; + default: + throw new InvalidOperationException(nameof(Node)); + } + } + } + + public void SerializeAsChild(BinaryWriter writer) + { + switch (type) + { + case NodeType.BranchNode: + case NodeType.ExtensionNode: + case NodeType.LeafNode: + var n = NewHash(Hash); + n.Serialize(writer); + break; + case NodeType.HashNode: + case NodeType.Empty: + Serialize(writer); + break; + default: + throw new FormatException(nameof(SerializeAsChild)); + } + } + + private void SerializeWithoutReference(BinaryWriter writer) + { + writer.Write((byte)type); + switch (type) + { + case NodeType.BranchNode: + SerializeBranch(writer); + break; + case NodeType.ExtensionNode: + SerializeExtension(writer); + break; + case NodeType.LeafNode: + SerializeLeaf(writer); + break; + case NodeType.HashNode: + SerializeHash(writer); + break; + case NodeType.Empty: + break; + default: + throw new FormatException(nameof(SerializeWithoutReference)); + } + } + + public void Serialize(BinaryWriter writer) + { + SerializeWithoutReference(writer); + if (type == NodeType.BranchNode || type == NodeType.ExtensionNode || type == NodeType.LeafNode) + writer.WriteVarInt(Reference); + } + + public byte[] ToArrayWithoutReference() + { + using MemoryStream ms = new MemoryStream(); + using BinaryWriter writer = new BinaryWriter(ms, Utility.StrictUTF8, true); + + SerializeWithoutReference(writer); + writer.Flush(); + return ms.ToArray(); + } + + public void Deserialize(ref MemoryReader reader) + { + type = (NodeType)reader.ReadByte(); + switch (type) + { + case NodeType.BranchNode: + DeserializeBranch(ref reader); + Reference = (int)reader.ReadVarInt(); + break; + case NodeType.ExtensionNode: + DeserializeExtension(ref reader); + Reference = (int)reader.ReadVarInt(); + break; + case NodeType.LeafNode: + DeserializeLeaf(ref reader); + Reference = (int)reader.ReadVarInt(); + break; + case NodeType.Empty: + break; + case NodeType.HashNode: + DeserializeHash(ref reader); + break; + default: + throw new FormatException(nameof(Deserialize)); + } + } + + private Node CloneAsChild() + { + switch (type) + { + case NodeType.BranchNode: + case NodeType.ExtensionNode: + case NodeType.LeafNode: + return new Node + { + type = NodeType.HashNode, + hash = Hash, + }; + case NodeType.HashNode: + case NodeType.Empty: + return Clone(); + default: + throw new InvalidOperationException(nameof(Clone)); + } + } + + public Node Clone() + { + switch (type) + { + case NodeType.BranchNode: + var n = new Node + { + type = type, + Reference = Reference, + Children = new Node[BranchChildCount], + }; + for (int i = 0; i < BranchChildCount; i++) + { + n.Children[i] = Children[i].CloneAsChild(); + } + return n; + case NodeType.ExtensionNode: + return new Node + { + type = type, + Key = Key, + Next = Next.CloneAsChild(), + Reference = Reference, + }; + case NodeType.LeafNode: + return new Node + { + type = type, + Value = Value, + Reference = Reference, + }; + case NodeType.HashNode: + case NodeType.Empty: + return this; + default: + throw new InvalidOperationException(nameof(Clone)); + } + } + + public void FromReplica(Node n) + { + MemoryReader reader = new(n.ToArray()); + Deserialize(ref reader); + } + } +} diff --git a/src/Plugins/MPTTrie/Cryptography/MPTTrie/NodeType.cs b/src/Plugins/MPTTrie/Cryptography/MPTTrie/NodeType.cs new file mode 100644 index 0000000000..9c676ff2f7 --- /dev/null +++ b/src/Plugins/MPTTrie/Cryptography/MPTTrie/NodeType.cs @@ -0,0 +1,22 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// NodeType.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.Cryptography.MPTTrie +{ + public enum NodeType : byte + { + BranchNode = 0x00, + ExtensionNode = 0x01, + LeafNode = 0x02, + HashNode = 0x03, + Empty = 0x04 + } +} diff --git a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Delete.cs b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Delete.cs new file mode 100644 index 0000000000..2041100a14 --- /dev/null +++ b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Delete.cs @@ -0,0 +1,136 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Trie.Delete.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Collections.Generic; +using static Neo.Helper; + +namespace Neo.Cryptography.MPTTrie +{ + partial class Trie + { + public bool Delete(byte[] key) + { + var path = ToNibbles(key); + if (path.Length == 0) + throw new ArgumentException("could not be empty", nameof(key)); + if (path.Length > Node.MaxKeyLength) + throw new ArgumentException("exceeds limit", nameof(key)); + return TryDelete(ref root, path); + } + + private bool TryDelete(ref Node node, ReadOnlySpan path) + { + switch (node.Type) + { + case NodeType.LeafNode: + { + if (path.IsEmpty) + { + if (!full) cache.DeleteNode(node.Hash); + node = new Node(); + return true; + } + return false; + } + case NodeType.ExtensionNode: + { + if (path.StartsWith(node.Key.Span)) + { + var oldHash = node.Hash; + var result = TryDelete(ref node.Next, path[node.Key.Length..]); + if (!result) return false; + if (!full) cache.DeleteNode(oldHash); + if (node.Next.IsEmpty) + { + node = node.Next; + return true; + } + if (node.Next.Type == NodeType.ExtensionNode) + { + if (!full) cache.DeleteNode(node.Next.Hash); + node.Key = Concat(node.Key.Span, node.Next.Key.Span); + node.Next = node.Next.Next; + } + node.SetDirty(); + cache.PutNode(node); + return true; + } + return false; + } + case NodeType.BranchNode: + { + bool result; + var oldHash = node.Hash; + if (path.IsEmpty) + { + result = TryDelete(ref node.Children[Node.BranchChildCount - 1], path); + } + else + { + result = TryDelete(ref node.Children[path[0]], path[1..]); + } + if (!result) return false; + if (!full) cache.DeleteNode(oldHash); + List childrenIndexes = new List(Node.BranchChildCount); + for (int i = 0; i < Node.BranchChildCount; i++) + { + if (node.Children[i].IsEmpty) continue; + childrenIndexes.Add((byte)i); + } + if (childrenIndexes.Count > 1) + { + node.SetDirty(); + cache.PutNode(node); + return true; + } + var lastChildIndex = childrenIndexes[0]; + var lastChild = node.Children[lastChildIndex]; + if (lastChildIndex == Node.BranchChildCount - 1) + { + node = lastChild; + return true; + } + if (lastChild.Type == NodeType.HashNode) + { + lastChild = cache.Resolve(lastChild.Hash); + if (lastChild is null) throw new InvalidOperationException("Internal error, can't resolve hash"); + } + if (lastChild.Type == NodeType.ExtensionNode) + { + if (!full) cache.DeleteNode(lastChild.Hash); + lastChild.Key = Concat(childrenIndexes.ToArray(), lastChild.Key.Span); + lastChild.SetDirty(); + cache.PutNode(lastChild); + node = lastChild; + return true; + } + node = Node.NewExtension(childrenIndexes.ToArray(), lastChild); + cache.PutNode(node); + return true; + } + case NodeType.Empty: + { + return false; + } + case NodeType.HashNode: + { + var newNode = cache.Resolve(node.Hash); + if (newNode is null) throw new InvalidOperationException("Internal error, can't resolve hash when mpt delete"); + node = newNode; + return TryDelete(ref node, path); + } + default: + return false; + } + } + } +} diff --git a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Find.cs b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Find.cs new file mode 100644 index 0000000000..b3922e8ce8 --- /dev/null +++ b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Find.cs @@ -0,0 +1,170 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Trie.Find.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Collections.Generic; +using System.Linq; +using static Neo.Helper; + +namespace Neo.Cryptography.MPTTrie +{ + partial class Trie + { + private ReadOnlySpan Seek(ref Node node, ReadOnlySpan path, out Node start) + { + switch (node.Type) + { + case NodeType.LeafNode: + { + if (path.IsEmpty) + { + start = node; + return ReadOnlySpan.Empty; + } + break; + } + case NodeType.Empty: + break; + case NodeType.HashNode: + { + var newNode = cache.Resolve(node.Hash); + if (newNode is null) throw new InvalidOperationException("Internal error, can't resolve hash when mpt seek"); + node = newNode; + return Seek(ref node, path, out start); + } + case NodeType.BranchNode: + { + if (path.IsEmpty) + { + start = node; + return ReadOnlySpan.Empty; + } + return Concat(path[..1], Seek(ref node.Children[path[0]], path[1..], out start)); + } + case NodeType.ExtensionNode: + { + if (path.IsEmpty) + { + start = node.Next; + return node.Key.Span; + } + if (path.StartsWith(node.Key.Span)) + { + return Concat(node.Key.Span, Seek(ref node.Next, path[node.Key.Length..], out start)); + } + if (node.Key.Span.StartsWith(path)) + { + start = node.Next; + return node.Key.Span; + } + break; + } + } + start = null; + return ReadOnlySpan.Empty; + } + + public IEnumerable<(ReadOnlyMemory Key, ReadOnlyMemory Value)> Find(ReadOnlySpan prefix, byte[] from = null) + { + var path = ToNibbles(prefix); + int offset = 0; + if (from is null) from = Array.Empty(); + if (0 < from.Length) + { + if (!from.AsSpan().StartsWith(prefix)) + throw new InvalidOperationException("invalid from key"); + from = ToNibbles(from.AsSpan()); + } + if (path.Length > Node.MaxKeyLength || from.Length > Node.MaxKeyLength) + throw new ArgumentException("exceeds limit"); + path = Seek(ref root, path, out Node start).ToArray(); + if (from.Length > 0) + { + for (int i = 0; i < from.Length && i < path.Length; i++) + { + if (path[i] < from[i]) return Enumerable.Empty<(ReadOnlyMemory, ReadOnlyMemory)>(); + if (path[i] > from[i]) + { + offset = from.Length; + break; + } + } + if (offset == 0) + { + offset = Math.Min(path.Length, from.Length); + } + } + return Travers(start, path, from, offset).Select(p => (new ReadOnlyMemory(FromNibbles(p.Key.Span)), p.Value)); + } + + private IEnumerable<(ReadOnlyMemory Key, ReadOnlyMemory Value)> Travers(Node node, byte[] path, byte[] from, int offset) + { + if (node is null) yield break; + if (offset < 0) throw new InvalidOperationException("invalid offset"); + switch (node.Type) + { + case NodeType.LeafNode: + { + if (from.Length <= offset && !path.SequenceEqual(from)) + yield return (path, node.Value); + break; + } + case NodeType.Empty: + break; + case NodeType.HashNode: + { + var newNode = cache.Resolve(node.Hash); + if (newNode is null) throw new InvalidOperationException("Internal error, can't resolve hash when mpt find"); + node = newNode; + foreach (var item in Travers(node, path, from, offset)) + yield return item; + break; + } + case NodeType.BranchNode: + { + if (offset < from.Length) + { + for (int i = 0; i < Node.BranchChildCount - 1; i++) + { + if (from[offset] < i) + foreach (var item in Travers(node.Children[i], Concat(path, new byte[] { (byte)i }), from, from.Length)) + yield return item; + else if (i == from[offset]) + foreach (var item in Travers(node.Children[i], Concat(path, new byte[] { (byte)i }), from, offset + 1)) + yield return item; + } + } + else + { + foreach (var item in Travers(node.Children[Node.BranchChildCount - 1], path, from, offset)) + yield return item; + for (int i = 0; i < Node.BranchChildCount - 1; i++) + { + foreach (var item in Travers(node.Children[i], Concat(path, new byte[] { (byte)i }), from, offset)) + yield return item; + } + } + break; + } + case NodeType.ExtensionNode: + { + if (offset < from.Length && from.AsSpan()[offset..].StartsWith(node.Key.Span)) + foreach (var item in Travers(node.Next, Concat(path, node.Key.Span), from, offset + node.Key.Length)) + yield return item; + else if (from.Length <= offset || 0 < node.Key.Span.CompareTo(from.AsSpan(offset))) + foreach (var item in Travers(node.Next, Concat(path, node.Key.Span), from, from.Length)) + yield return item; + break; + } + } + } + } +} diff --git a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Get.cs b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Get.cs new file mode 100644 index 0000000000..da69407ac3 --- /dev/null +++ b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Get.cs @@ -0,0 +1,90 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Trie.Get.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Collections.Generic; + +namespace Neo.Cryptography.MPTTrie +{ + partial class Trie + { + public byte[] this[byte[] key] + { + get + { + var path = ToNibbles(key); + if (path.Length == 0) + throw new ArgumentException("could not be empty", nameof(key)); + if (path.Length > Node.MaxKeyLength) + throw new ArgumentException("exceeds limit", nameof(key)); + var result = TryGet(ref root, path, out var value); + return result ? value.ToArray() : throw new KeyNotFoundException(); + } + } + + public bool TryGetValue(byte[] key, out byte[] value) + { + value = default; + var path = ToNibbles(key); + if (path.Length == 0) + throw new ArgumentException("could not be empty", nameof(key)); + if (path.Length > Node.MaxKeyLength) + throw new ArgumentException("exceeds limit", nameof(key)); + var result = TryGet(ref root, path, out var val); + if (result) + value = val.ToArray(); + return result; + } + + private bool TryGet(ref Node node, ReadOnlySpan path, out ReadOnlySpan value) + { + switch (node.Type) + { + case NodeType.LeafNode: + { + if (path.IsEmpty) + { + value = node.Value.Span; + return true; + } + break; + } + case NodeType.Empty: + break; + case NodeType.HashNode: + { + var newNode = cache.Resolve(node.Hash); + if (newNode is null) throw new InvalidOperationException("Internal error, can't resolve hash when mpt get"); + node = newNode; + return TryGet(ref node, path, out value); + } + case NodeType.BranchNode: + { + if (path.IsEmpty) + { + return TryGet(ref node.Children[Node.BranchChildCount - 1], path, out value); + } + return TryGet(ref node.Children[path[0]], path[1..], out value); + } + case NodeType.ExtensionNode: + { + if (path.StartsWith(node.Key.Span)) + { + return TryGet(ref node.Next, path[node.Key.Length..], out value); + } + break; + } + } + value = default; + return false; + } + } +} diff --git a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Proof.cs b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Proof.cs new file mode 100644 index 0000000000..e0925452e3 --- /dev/null +++ b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Proof.cs @@ -0,0 +1,95 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Trie.Proof.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using Neo.Persistence; +using System; +using System.Collections.Generic; +using static Neo.Helper; + +namespace Neo.Cryptography.MPTTrie +{ + partial class Trie + { + public bool TryGetProof(byte[] key, out HashSet proof) + { + var path = ToNibbles(key); + if (path.Length == 0) + throw new ArgumentException("could not be empty", nameof(key)); + if (path.Length > Node.MaxKeyLength) + throw new ArgumentException("exceeds limit", nameof(key)); + proof = new HashSet(ByteArrayEqualityComparer.Default); + return GetProof(ref root, path, proof); + } + + private bool GetProof(ref Node node, ReadOnlySpan path, HashSet set) + { + switch (node.Type) + { + case NodeType.LeafNode: + { + if (path.IsEmpty) + { + set.Add(node.ToArrayWithoutReference()); + return true; + } + break; + } + case NodeType.Empty: + break; + case NodeType.HashNode: + { + var newNode = cache.Resolve(node.Hash); + if (newNode is null) throw new InvalidOperationException("Internal error, can't resolve hash when mpt getproof"); + node = newNode; + return GetProof(ref node, path, set); + } + case NodeType.BranchNode: + { + set.Add(node.ToArrayWithoutReference()); + if (path.IsEmpty) + { + return GetProof(ref node.Children[Node.BranchChildCount - 1], path, set); + } + return GetProof(ref node.Children[path[0]], path[1..], set); + } + case NodeType.ExtensionNode: + { + if (path.StartsWith(node.Key.Span)) + { + set.Add(node.ToArrayWithoutReference()); + return GetProof(ref node.Next, path[node.Key.Length..], set); + } + break; + } + } + return false; + } + + private static byte[] Key(byte[] hash) + { + byte[] buffer = new byte[hash.Length + 1]; + buffer[0] = Prefix; + Buffer.BlockCopy(hash, 0, buffer, 1, hash.Length); + return buffer; + } + + public static byte[] VerifyProof(UInt256 root, byte[] key, HashSet proof) + { + using var memoryStore = new MemoryStore(); + foreach (byte[] data in proof) + memoryStore.Put(Key(Crypto.Hash256(data)), Concat(data, new byte[] { 1 })); + using ISnapshot snapshot = memoryStore.GetSnapshot(); + var trie = new Trie(snapshot, root, false); + return trie[key]; + } + } +} diff --git a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Put.cs b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Put.cs new file mode 100644 index 0000000000..5de6f3fc85 --- /dev/null +++ b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Put.cs @@ -0,0 +1,159 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Trie.Put.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; + +namespace Neo.Cryptography.MPTTrie +{ + partial class Trie + { + private static ReadOnlySpan CommonPrefix(ReadOnlySpan a, ReadOnlySpan b) + { + var minLen = a.Length <= b.Length ? a.Length : b.Length; + int i = 0; + if (a.Length != 0 && b.Length != 0) + { + for (i = 0; i < minLen; i++) + { + if (a[i] != b[i]) break; + } + } + return a[..i]; + } + + public void Put(byte[] key, byte[] value) + { + var path = ToNibbles(key); + var val = value; + if (path.Length == 0 || path.Length > Node.MaxKeyLength) + throw new ArgumentException("invalid", nameof(key)); + if (val.Length > Node.MaxValueLength) + throw new ArgumentException("exceed limit", nameof(value)); + var n = Node.NewLeaf(val); + Put(ref root, path, n); + } + + private void Put(ref Node node, ReadOnlySpan path, Node val) + { + switch (node.Type) + { + case NodeType.LeafNode: + { + if (path.IsEmpty) + { + if (!full) cache.DeleteNode(node.Hash); + node = val; + cache.PutNode(node); + return; + } + var branch = Node.NewBranch(); + branch.Children[Node.BranchChildCount - 1] = node; + Put(ref branch.Children[path[0]], path[1..], val); + cache.PutNode(branch); + node = branch; + break; + } + case NodeType.ExtensionNode: + { + if (path.StartsWith(node.Key.Span)) + { + var oldHash = node.Hash; + Put(ref node.Next, path[node.Key.Length..], val); + if (!full) cache.DeleteNode(oldHash); + node.SetDirty(); + cache.PutNode(node); + return; + } + if (!full) cache.DeleteNode(node.Hash); + var prefix = CommonPrefix(node.Key.Span, path); + var pathRemain = path[prefix.Length..]; + var keyRemain = node.Key.Span[prefix.Length..]; + var child = Node.NewBranch(); + Node grandChild = new Node(); + if (keyRemain.Length == 1) + { + child.Children[keyRemain[0]] = node.Next; + } + else + { + var exNode = Node.NewExtension(keyRemain[1..].ToArray(), node.Next); + cache.PutNode(exNode); + child.Children[keyRemain[0]] = exNode; + } + if (pathRemain.IsEmpty) + { + Put(ref grandChild, pathRemain, val); + child.Children[Node.BranchChildCount - 1] = grandChild; + } + else + { + Put(ref grandChild, pathRemain[1..], val); + child.Children[pathRemain[0]] = grandChild; + } + cache.PutNode(child); + if (prefix.Length > 0) + { + var exNode = Node.NewExtension(prefix.ToArray(), child); + cache.PutNode(exNode); + node = exNode; + } + else + { + node = child; + } + break; + } + case NodeType.BranchNode: + { + var oldHash = node.Hash; + if (path.IsEmpty) + { + Put(ref node.Children[Node.BranchChildCount - 1], path, val); + } + else + { + Put(ref node.Children[path[0]], path[1..], val); + } + if (!full) cache.DeleteNode(oldHash); + node.SetDirty(); + cache.PutNode(node); + break; + } + case NodeType.Empty: + { + Node newNode; + if (path.IsEmpty) + { + newNode = val; + } + else + { + newNode = Node.NewExtension(path.ToArray(), val); + cache.PutNode(newNode); + } + node = newNode; + if (val.Type == NodeType.LeafNode) cache.PutNode(val); + break; + } + case NodeType.HashNode: + { + Node newNode = cache.Resolve(node.Hash); + if (newNode is null) throw new InvalidOperationException("Internal error, can't resolve hash when mpt put"); + node = newNode; + Put(ref node, path, val); + break; + } + default: + throw new InvalidOperationException("unsupport node type"); + } + } + } +} diff --git a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.cs b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.cs new file mode 100644 index 0000000000..19ef1c8b4c --- /dev/null +++ b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.cs @@ -0,0 +1,62 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Trie.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Persistence; +using System; + +namespace Neo.Cryptography.MPTTrie +{ + public partial class Trie + { + private const byte Prefix = 0xf0; + private readonly bool full; + private readonly ISnapshot store; + private Node root; + private readonly Cache cache; + public Node Root => root; + + public Trie(ISnapshot store, UInt256 root, bool full_state = false) + { + this.store = store ?? throw new ArgumentNullException(nameof(store)); + cache = new Cache(store, Prefix); + this.root = root is null ? new Node() : Node.NewHash(root); + full = full_state; + } + + private static byte[] ToNibbles(ReadOnlySpan path) + { + var result = new byte[path.Length * 2]; + for (int i = 0; i < path.Length; i++) + { + result[i * 2] = (byte)(path[i] >> 4); + result[i * 2 + 1] = (byte)(path[i] & 0x0F); + } + return result; + } + + private static byte[] FromNibbles(ReadOnlySpan path) + { + if (path.Length % 2 != 0) throw new FormatException($"MPTTrie.FromNibbles invalid path."); + var key = new byte[path.Length / 2]; + for (int i = 0; i < key.Length; i++) + { + key[i] = (byte)(path[i * 2] << 4); + key[i] |= path[i * 2 + 1]; + } + return key; + } + + public void Commit() + { + cache.Commit(); + } + } +} diff --git a/src/Neo/IO/ByteArrayEqualityComparer.cs b/src/Plugins/MPTTrie/IO/ByteArrayEqualityComparer.cs similarity index 77% rename from src/Neo/IO/ByteArrayEqualityComparer.cs rename to src/Plugins/MPTTrie/IO/ByteArrayEqualityComparer.cs index 2085bd2291..590306d560 100644 --- a/src/Neo/IO/ByteArrayEqualityComparer.cs +++ b/src/Plugins/MPTTrie/IO/ByteArrayEqualityComparer.cs @@ -1,8 +1,9 @@ -// Copyright (C) 2015-2022 The Neo Project. +// Copyright (C) 2015-2024 The Neo Project. // -// The neo is free software distributed under the MIT software license, -// see the accompanying file LICENSE in the main directory of the -// project or http://www.opensource.org/licenses/mit-license.php +// ByteArrayEqualityComparer.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php // for more details. // // Redistribution and use in source and binary forms with or without @@ -12,9 +13,9 @@ namespace Neo.IO { - internal class ByteArrayEqualityComparer : IEqualityComparer + public class ByteArrayEqualityComparer : IEqualityComparer { - public static readonly ByteArrayEqualityComparer Default = new(); + public static readonly ByteArrayEqualityComparer Default = new ByteArrayEqualityComparer(); public unsafe bool Equals(byte[] x, byte[] y) { diff --git a/src/Plugins/MPTTrie/MPTTrie.csproj b/src/Plugins/MPTTrie/MPTTrie.csproj new file mode 100644 index 0000000000..3134f7ae5b --- /dev/null +++ b/src/Plugins/MPTTrie/MPTTrie.csproj @@ -0,0 +1,11 @@ + + + + net8.0 + Neo.Cryptography.MPT + Neo.Cryptography + true + ../../../bin/$(PackageId) + + + diff --git a/src/Plugins/OracleService/Helper.cs b/src/Plugins/OracleService/Helper.cs new file mode 100644 index 0000000000..a4711b848e --- /dev/null +++ b/src/Plugins/OracleService/Helper.cs @@ -0,0 +1,52 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Helper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Linq; +using System.Net; + +namespace Neo.Plugins.OracleService +{ + static class Helper + { + public static bool IsInternal(this IPHostEntry entry) + { + return entry.AddressList.Any(p => IsInternal(p)); + } + + /// + /// ::1 - IPv6 loopback + /// 10.0.0.0 - 10.255.255.255 (10/8 prefix) + /// 127.0.0.0 - 127.255.255.255 (127/8 prefix) + /// 172.16.0.0 - 172.31.255.255 (172.16/12 prefix) + /// 192.168.0.0 - 192.168.255.255 (192.168/16 prefix) + /// + /// Address + /// True if it was an internal address + public static bool IsInternal(this IPAddress ipAddress) + { + if (IPAddress.IsLoopback(ipAddress)) return true; + if (IPAddress.Broadcast.Equals(ipAddress)) return true; + if (IPAddress.Any.Equals(ipAddress)) return true; + if (IPAddress.IPv6Any.Equals(ipAddress)) return true; + if (IPAddress.IPv6Loopback.Equals(ipAddress)) return true; + + var ip = ipAddress.GetAddressBytes(); + switch (ip[0]) + { + case 10: + case 127: return true; + case 172: return ip[1] >= 16 && ip[1] < 32; + case 192: return ip[1] == 168; + default: return false; + } + } + } +} diff --git a/src/Plugins/OracleService/OracleService.cs b/src/Plugins/OracleService/OracleService.cs new file mode 100644 index 0000000000..e9787b3d4c --- /dev/null +++ b/src/Plugins/OracleService/OracleService.cs @@ -0,0 +1,589 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// OracleService.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Akka.Actor; +using Akka.Util.Internal; +using Neo.ConsoleService; +using Neo.Cryptography; +using Neo.Cryptography.ECC; +using Neo.IO; +using Neo.Json; +using Neo.Ledger; +using Neo.Network.P2P; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.Plugins.RpcServer; +using Neo.SmartContract; +using Neo.SmartContract.Manifest; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Neo.Plugins.OracleService +{ + public class OracleService : Plugin + { + private const int RefreshIntervalMilliSeconds = 1000 * 60 * 3; + + private static readonly HttpClient httpClient = new() + { + Timeout = TimeSpan.FromSeconds(5), + MaxResponseContentBufferSize = ushort.MaxValue + }; + + private Wallet wallet; + private readonly ConcurrentDictionary pendingQueue = new ConcurrentDictionary(); + private readonly ConcurrentDictionary finishedCache = new ConcurrentDictionary(); + private Timer timer; + private readonly CancellationTokenSource cancelSource = new CancellationTokenSource(); + private OracleStatus status = OracleStatus.Unstarted; + private IWalletProvider walletProvider; + private int counter; + private NeoSystem _system; + + private readonly Dictionary protocols = new Dictionary(); + + public override string Description => "Built-in oracle plugin"; + + public override string ConfigFile => System.IO.Path.Combine(RootPath, "OracleService.json"); + + public OracleService() + { + Blockchain.Committing += OnCommitting; + } + + protected override void Configure() + { + Settings.Load(GetConfiguration()); + foreach (var (_, p) in protocols) + p.Configure(); + } + + protected override void OnSystemLoaded(NeoSystem system) + { + if (system.Settings.Network != Settings.Default.Network) return; + _system = system; + _system.ServiceAdded += NeoSystem_ServiceAdded; + RpcServerPlugin.RegisterMethods(this, Settings.Default.Network); + } + + private void NeoSystem_ServiceAdded(object sender, object service) + { + if (service is IWalletProvider) + { + walletProvider = service as IWalletProvider; + _system.ServiceAdded -= NeoSystem_ServiceAdded; + if (Settings.Default.AutoStart) + { + walletProvider.WalletChanged += WalletProvider_WalletChanged; + } + } + } + + private void WalletProvider_WalletChanged(object sender, Wallet wallet) + { + walletProvider.WalletChanged -= WalletProvider_WalletChanged; + Start(wallet); + } + + public override void Dispose() + { + Blockchain.Committing -= OnCommitting; + OnStop(); + while (status != OracleStatus.Stopped) + Thread.Sleep(100); + foreach (var p in protocols) + p.Value.Dispose(); + } + + [ConsoleCommand("start oracle", Category = "Oracle", Description = "Start oracle service")] + private void OnStart() + { + Start(walletProvider?.GetWallet()); + } + + public void Start(Wallet wallet) + { + if (status == OracleStatus.Running) return; + + if (wallet is null) + { + ConsoleHelper.Warning("Please open wallet first!"); + return; + } + + if (!CheckOracleAvaiblable(_system.StoreView, out ECPoint[] oracles)) + { + ConsoleHelper.Warning("The oracle service is unavailable"); + return; + } + if (!CheckOracleAccount(wallet, oracles)) + { + ConsoleHelper.Warning("There is no oracle account in wallet"); + return; + } + + this.wallet = wallet; + protocols["https"] = new OracleHttpsProtocol(); + protocols["neofs"] = new OracleNeoFSProtocol(wallet, oracles); + status = OracleStatus.Running; + timer = new Timer(OnTimer, null, RefreshIntervalMilliSeconds, Timeout.Infinite); + ConsoleHelper.Info($"Oracle started"); + ProcessRequestsAsync(); + } + + [ConsoleCommand("stop oracle", Category = "Oracle", Description = "Stop oracle service")] + private void OnStop() + { + cancelSource.Cancel(); + if (timer != null) + { + timer.Dispose(); + timer = null; + } + status = OracleStatus.Stopped; + } + + [ConsoleCommand("oracle status", Category = "Oracle", Description = "Show oracle status")] + private void OnShow() + { + ConsoleHelper.Info($"Oracle status: ", $"{status}"); + } + + private void OnCommitting(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) + { + if (system.Settings.Network != Settings.Default.Network) return; + + if (Settings.Default.AutoStart && status == OracleStatus.Unstarted) + { + OnStart(); + } + if (status != OracleStatus.Running) return; + if (!CheckOracleAvaiblable(snapshot, out ECPoint[] oracles) || !CheckOracleAccount(wallet, oracles)) + OnStop(); + } + + private async void OnTimer(object state) + { + try + { + List outOfDate = new(); + List tasks = new(); + foreach (var (id, task) in pendingQueue) + { + var span = TimeProvider.Current.UtcNow - task.Timestamp; + if (span > Settings.Default.MaxTaskTimeout) + { + outOfDate.Add(id); + continue; + } + + if (span > TimeSpan.FromMilliseconds(RefreshIntervalMilliSeconds)) + { + foreach (var account in wallet.GetAccounts()) + if (task.BackupSigns.TryGetValue(account.GetKey().PublicKey, out byte[] sign)) + tasks.Add(SendResponseSignatureAsync(id, sign, account.GetKey())); + } + } + + await Task.WhenAll(tasks); + + foreach (ulong requestId in outOfDate) + pendingQueue.TryRemove(requestId, out _); + foreach (var (key, value) in finishedCache) + if (TimeProvider.Current.UtcNow - value > TimeSpan.FromDays(3)) + finishedCache.TryRemove(key, out _); + } + catch (Exception e) + { + Log(e, LogLevel.Error); + } + finally + { + if (!cancelSource.IsCancellationRequested) + timer?.Change(RefreshIntervalMilliSeconds, Timeout.Infinite); + } + } + + [RpcMethod] + public JObject SubmitOracleResponse(JArray _params) + { + status.Equals(OracleStatus.Running).True_Or(RpcError.OracleDisabled); + ECPoint oraclePub = ECPoint.DecodePoint(Convert.FromBase64String(_params[0].AsString()), ECCurve.Secp256r1); + ulong requestId = Result.Ok_Or(() => (ulong)_params[1].AsNumber(), RpcError.InvalidParams.WithData($"Invalid requestId: {_params[1]}")); + byte[] txSign = Result.Ok_Or(() => Convert.FromBase64String(_params[2].AsString()), RpcError.InvalidParams.WithData($"Invalid txSign: {_params[2]}")); + byte[] msgSign = Result.Ok_Or(() => Convert.FromBase64String(_params[3].AsString()), RpcError.InvalidParams.WithData($"Invalid msgSign: {_params[3]}")); + + finishedCache.ContainsKey(requestId).False_Or(RpcError.OracleRequestFinished); + + using (var snapshot = _system.GetSnapshot()) + { + uint height = NativeContract.Ledger.CurrentIndex(snapshot) + 1; + var oracles = NativeContract.RoleManagement.GetDesignatedByRole(snapshot, Role.Oracle, height); + oracles.Any(p => p.Equals(oraclePub)).True_Or(RpcErrorFactory.OracleNotDesignatedNode(oraclePub)); + NativeContract.Oracle.GetRequest(snapshot, requestId).NotNull_Or(RpcError.OracleRequestNotFound); + var data = Neo.Helper.Concat(oraclePub.ToArray(), BitConverter.GetBytes(requestId), txSign); + Crypto.VerifySignature(data, msgSign, oraclePub).True_Or(RpcErrorFactory.InvalidSignature($"Invalid oracle response transaction signature from '{oraclePub}'.")); + AddResponseTxSign(snapshot, requestId, oraclePub, txSign); + } + return new JObject(); + } + + private static async Task SendContentAsync(Uri url, string content) + { + try + { + using HttpResponseMessage response = await httpClient.PostAsync(url, new StringContent(content, Encoding.UTF8, "application/json")); + response.EnsureSuccessStatusCode(); + } + catch (Exception e) + { + Log($"Failed to send the response signature to {url}, as {e.Message}", LogLevel.Warning); + } + } + + private async Task SendResponseSignatureAsync(ulong requestId, byte[] txSign, KeyPair keyPair) + { + var message = Neo.Helper.Concat(keyPair.PublicKey.ToArray(), BitConverter.GetBytes(requestId), txSign); + var sign = Crypto.Sign(message, keyPair.PrivateKey); + var param = "\"" + Convert.ToBase64String(keyPair.PublicKey.ToArray()) + "\", " + requestId + ", \"" + Convert.ToBase64String(txSign) + "\",\"" + Convert.ToBase64String(sign) + "\""; + var content = "{\"id\":" + Interlocked.Increment(ref counter) + ",\"jsonrpc\":\"2.0\",\"method\":\"submitoracleresponse\",\"params\":[" + param + "]}"; + + var tasks = Settings.Default.Nodes.Select(p => SendContentAsync(p, content)); + await Task.WhenAll(tasks); + } + + private async Task ProcessRequestAsync(DataCache snapshot, OracleRequest req) + { + Log($"[{req.OriginalTxid}] Process oracle request start:<{req.Url}>"); + + uint height = NativeContract.Ledger.CurrentIndex(snapshot) + 1; + + (OracleResponseCode code, string data) = await ProcessUrlAsync(req.Url); + + Log($"[{req.OriginalTxid}] Process oracle request end:<{req.Url}>, responseCode:{code}, response:{data}"); + + var oracleNodes = NativeContract.RoleManagement.GetDesignatedByRole(snapshot, Role.Oracle, height); + foreach (var (requestId, request) in NativeContract.Oracle.GetRequestsByUrl(snapshot, req.Url)) + { + var result = Array.Empty(); + if (code == OracleResponseCode.Success) + { + try + { + result = Filter(data, request.Filter); + } + catch (Exception ex) + { + code = OracleResponseCode.Error; + Log($"[{req.OriginalTxid}] Filter '{request.Filter}' error:{ex.Message}"); + } + } + var response = new OracleResponse() { Id = requestId, Code = code, Result = result }; + var responseTx = CreateResponseTx(snapshot, request, response, oracleNodes, _system.Settings); + var backupTx = CreateResponseTx(snapshot, request, new OracleResponse() { Code = OracleResponseCode.ConsensusUnreachable, Id = requestId, Result = Array.Empty() }, oracleNodes, _system.Settings, true); + + Log($"[{req.OriginalTxid}]-({requestId}) Built response tx[[{responseTx.Hash}]], responseCode:{code}, result:{result.ToHexString()}, validUntilBlock:{responseTx.ValidUntilBlock}, backupTx:{backupTx.Hash}-{backupTx.ValidUntilBlock}"); + + List tasks = new List(); + ECPoint[] oraclePublicKeys = NativeContract.RoleManagement.GetDesignatedByRole(snapshot, Role.Oracle, height); + foreach (var account in wallet.GetAccounts()) + { + var oraclePub = account.GetKey()?.PublicKey; + if (!account.HasKey || account.Lock || !oraclePublicKeys.Contains(oraclePub)) continue; + + var txSign = responseTx.Sign(account.GetKey(), _system.Settings.Network); + var backTxSign = backupTx.Sign(account.GetKey(), _system.Settings.Network); + + AddResponseTxSign(snapshot, requestId, oraclePub, txSign, responseTx, backupTx, backTxSign); + tasks.Add(SendResponseSignatureAsync(requestId, txSign, account.GetKey())); + + Log($"[{request.OriginalTxid}]-[[{responseTx.Hash}]] Send oracle sign data, Oracle node: {oraclePub}, Sign: {txSign.ToHexString()}"); + } + await Task.WhenAll(tasks); + } + } + + private async void ProcessRequestsAsync() + { + while (!cancelSource.IsCancellationRequested) + { + using (var snapshot = _system.GetSnapshot()) + { + SyncPendingQueue(snapshot); + foreach (var (id, request) in NativeContract.Oracle.GetRequests(snapshot)) + { + if (cancelSource.IsCancellationRequested) break; + if (!finishedCache.ContainsKey(id) && (!pendingQueue.TryGetValue(id, out OracleTask task) || task.Tx is null)) + await ProcessRequestAsync(snapshot, request); + } + } + if (cancelSource.IsCancellationRequested) break; + await Task.Delay(500); + } + + status = OracleStatus.Stopped; + } + + + private void SyncPendingQueue(DataCache snapshot) + { + var offChainRequests = NativeContract.Oracle.GetRequests(snapshot).ToDictionary(r => r.Item1, r => r.Item2); + var onChainRequests = pendingQueue.Keys.Except(offChainRequests.Keys); + foreach (var onChainRequest in onChainRequests) + { + pendingQueue.TryRemove(onChainRequest, out _); + } + } + + private async Task<(OracleResponseCode, string)> ProcessUrlAsync(string url) + { + if (!Uri.TryCreate(url, UriKind.Absolute, out var uri)) + return (OracleResponseCode.Error, $"Invalid url:<{url}>"); + if (!protocols.TryGetValue(uri.Scheme, out IOracleProtocol protocol)) + return (OracleResponseCode.ProtocolNotSupported, $"Invalid Protocol:<{url}>"); + + using CancellationTokenSource ctsTimeout = new(Settings.Default.MaxOracleTimeout); + using CancellationTokenSource ctsLinked = CancellationTokenSource.CreateLinkedTokenSource(cancelSource.Token, ctsTimeout.Token); + + try + { + return await protocol.ProcessAsync(uri, ctsLinked.Token); + } + catch (Exception ex) + { + return (OracleResponseCode.Error, $"Request <{url}> Error:{ex.Message}"); + } + } + + public static Transaction CreateResponseTx(DataCache snapshot, OracleRequest request, OracleResponse response, ECPoint[] oracleNodes, ProtocolSettings settings, bool useCurrentHeight = false) + { + var requestTx = NativeContract.Ledger.GetTransactionState(snapshot, request.OriginalTxid); + var n = oracleNodes.Length; + var m = n - (n - 1) / 3; + var oracleSignContract = Contract.CreateMultiSigContract(m, oracleNodes); + uint height = NativeContract.Ledger.CurrentIndex(snapshot); + var validUntilBlock = requestTx.BlockIndex + settings.MaxValidUntilBlockIncrement; + while (useCurrentHeight && validUntilBlock <= height) + { + validUntilBlock += settings.MaxValidUntilBlockIncrement; + } + var tx = new Transaction() + { + Version = 0, + Nonce = unchecked((uint)response.Id), + ValidUntilBlock = validUntilBlock, + Signers = new[] + { + new Signer + { + Account = NativeContract.Oracle.Hash, + Scopes = WitnessScope.None + }, + new Signer + { + Account = oracleSignContract.ScriptHash, + Scopes = WitnessScope.None + } + }, + Attributes = new[] { response }, + Script = OracleResponse.FixedScript, + Witnesses = new Witness[2] + }; + Dictionary witnessDict = new Dictionary + { + [oracleSignContract.ScriptHash] = new Witness + { + InvocationScript = Array.Empty(), + VerificationScript = oracleSignContract.Script, + }, + [NativeContract.Oracle.Hash] = new Witness + { + InvocationScript = Array.Empty(), + VerificationScript = Array.Empty(), + } + }; + + UInt160[] hashes = tx.GetScriptHashesForVerifying(snapshot); + tx.Witnesses[0] = witnessDict[hashes[0]]; + tx.Witnesses[1] = witnessDict[hashes[1]]; + + // Calculate network fee + + var oracleContract = NativeContract.ContractManagement.GetContract(snapshot, NativeContract.Oracle.Hash); + var engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot.CreateSnapshot(), settings: settings); + ContractMethodDescriptor md = oracleContract.Manifest.Abi.GetMethod(ContractBasicMethod.Verify, ContractBasicMethod.VerifyPCount); + engine.LoadContract(oracleContract, md, CallFlags.None); + if (engine.Execute() != VMState.HALT) return null; + tx.NetworkFee += engine.FeeConsumed; + + var executionFactor = NativeContract.Policy.GetExecFeeFactor(snapshot); + var networkFee = executionFactor * SmartContract.Helper.MultiSignatureContractCost(m, n); + tx.NetworkFee += networkFee; + + // Base size for transaction: includes const_header + signers + script + hashes + witnesses, except attributes + + int size_inv = 66 * m; + int size = Transaction.HeaderSize + tx.Signers.GetVarSize() + tx.Script.GetVarSize() + + IO.Helper.GetVarSize(hashes.Length) + witnessDict[NativeContract.Oracle.Hash].Size + + IO.Helper.GetVarSize(size_inv) + size_inv + oracleSignContract.Script.GetVarSize(); + + var feePerByte = NativeContract.Policy.GetFeePerByte(snapshot); + if (response.Result.Length > OracleResponse.MaxResultSize) + { + response.Code = OracleResponseCode.ResponseTooLarge; + response.Result = Array.Empty(); + } + else if (tx.NetworkFee + (size + tx.Attributes.GetVarSize()) * feePerByte > request.GasForResponse) + { + response.Code = OracleResponseCode.InsufficientFunds; + response.Result = Array.Empty(); + } + size += tx.Attributes.GetVarSize(); + tx.NetworkFee += size * feePerByte; + + // Calcualte system fee + + tx.SystemFee = request.GasForResponse - tx.NetworkFee; + + return tx; + } + + private void AddResponseTxSign(DataCache snapshot, ulong requestId, ECPoint oraclePub, byte[] sign, Transaction responseTx = null, Transaction backupTx = null, byte[] backupSign = null) + { + var task = pendingQueue.GetOrAdd(requestId, _ => new OracleTask + { + Id = requestId, + Request = NativeContract.Oracle.GetRequest(snapshot, requestId), + Signs = new ConcurrentDictionary(), + BackupSigns = new ConcurrentDictionary() + }); + + if (responseTx != null) + { + task.Tx = responseTx; + var data = task.Tx.GetSignData(_system.Settings.Network); + task.Signs.Where(p => !Crypto.VerifySignature(data, p.Value, p.Key)).ForEach(p => task.Signs.Remove(p.Key, out _)); + } + if (backupTx != null) + { + task.BackupTx = backupTx; + var data = task.BackupTx.GetSignData(_system.Settings.Network); + task.BackupSigns.Where(p => !Crypto.VerifySignature(data, p.Value, p.Key)).ForEach(p => task.BackupSigns.Remove(p.Key, out _)); + task.BackupSigns.TryAdd(oraclePub, backupSign); + } + if (task.Tx == null) + { + task.Signs.TryAdd(oraclePub, sign); + task.BackupSigns.TryAdd(oraclePub, sign); + return; + } + + if (Crypto.VerifySignature(task.Tx.GetSignData(_system.Settings.Network), sign, oraclePub)) + task.Signs.TryAdd(oraclePub, sign); + else if (Crypto.VerifySignature(task.BackupTx.GetSignData(_system.Settings.Network), sign, oraclePub)) + task.BackupSigns.TryAdd(oraclePub, sign); + else + throw new RpcException(RpcErrorFactory.InvalidSignature($"Invalid oracle response transaction signature from '{oraclePub}'.")); + + if (CheckTxSign(snapshot, task.Tx, task.Signs) || CheckTxSign(snapshot, task.BackupTx, task.BackupSigns)) + { + finishedCache.TryAdd(requestId, new DateTime()); + pendingQueue.TryRemove(requestId, out _); + } + } + + public static byte[] Filter(string input, string filterArgs) + { + if (string.IsNullOrEmpty(filterArgs)) + return Utility.StrictUTF8.GetBytes(input); + + JToken beforeObject = JToken.Parse(input); + JArray afterObjects = beforeObject.JsonPath(filterArgs); + return afterObjects.ToByteArray(false); + } + + private bool CheckTxSign(DataCache snapshot, Transaction tx, ConcurrentDictionary OracleSigns) + { + uint height = NativeContract.Ledger.CurrentIndex(snapshot) + 1; + if (tx.ValidUntilBlock <= height) + { + return false; + } + ECPoint[] oraclesNodes = NativeContract.RoleManagement.GetDesignatedByRole(snapshot, Role.Oracle, height); + int neededThreshold = oraclesNodes.Length - (oraclesNodes.Length - 1) / 3; + if (OracleSigns.Count >= neededThreshold && tx != null) + { + var contract = Contract.CreateMultiSigContract(neededThreshold, oraclesNodes); + ScriptBuilder sb = new ScriptBuilder(); + foreach (var (_, sign) in OracleSigns.OrderBy(p => p.Key)) + { + sb.EmitPush(sign); + if (--neededThreshold == 0) break; + } + var idx = tx.GetScriptHashesForVerifying(snapshot)[0] == contract.ScriptHash ? 0 : 1; + tx.Witnesses[idx].InvocationScript = sb.ToArray(); + + Log($"Send response tx: responseTx={tx.Hash}"); + + _system.Blockchain.Tell(tx); + return true; + } + return false; + } + + private static bool CheckOracleAvaiblable(DataCache snapshot, out ECPoint[] oracles) + { + uint height = NativeContract.Ledger.CurrentIndex(snapshot) + 1; + oracles = NativeContract.RoleManagement.GetDesignatedByRole(snapshot, Role.Oracle, height); + return oracles.Length > 0; + } + + private static bool CheckOracleAccount(Wallet wallet, ECPoint[] oracles) + { + if (wallet is null) return false; + return oracles + .Select(p => wallet.GetAccount(p)) + .Any(p => p is not null && p.HasKey && !p.Lock); + } + + private static void Log(string message, LogLevel level = LogLevel.Info) + { + Utility.Log(nameof(OracleService), level, message); + } + + class OracleTask + { + public ulong Id; + public OracleRequest Request; + public Transaction Tx; + public Transaction BackupTx; + public ConcurrentDictionary Signs; + public ConcurrentDictionary BackupSigns; + public readonly DateTime Timestamp = TimeProvider.Current.UtcNow; + } + + enum OracleStatus + { + Unstarted, + Running, + Stopped, + } + } +} diff --git a/src/Plugins/OracleService/OracleService.csproj b/src/Plugins/OracleService/OracleService.csproj new file mode 100644 index 0000000000..bb5cde6754 --- /dev/null +++ b/src/Plugins/OracleService/OracleService.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + Neo.Plugins.OracleService + ../../../bin/$(PackageId) + + + + + + + + + + false + runtime + + + + + + PreserveNewest + + + + diff --git a/src/Plugins/OracleService/OracleService.json b/src/Plugins/OracleService/OracleService.json new file mode 100644 index 0000000000..1ab0d93399 --- /dev/null +++ b/src/Plugins/OracleService/OracleService.json @@ -0,0 +1,21 @@ +{ + "PluginConfiguration": { + "Network": 860833102, + "Nodes": [], + "MaxTaskTimeout": 432000000, + "MaxOracleTimeout": 10000, + "AllowPrivateHost": false, + "AllowedContentTypes": [ "application/json" ], + "Https": { + "Timeout": 5000 + }, + "NeoFS": { + "EndPoint": "http://127.0.0.1:8080", + "Timeout": 15000 + }, + "AutoStart": false + }, + "Dependency": [ + "RpcServer" + ] +} diff --git a/src/Plugins/OracleService/Protocols/IOracleProtocol.cs b/src/Plugins/OracleService/Protocols/IOracleProtocol.cs new file mode 100644 index 0000000000..d8b84660ee --- /dev/null +++ b/src/Plugins/OracleService/Protocols/IOracleProtocol.cs @@ -0,0 +1,24 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// IOracleProtocol.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Network.P2P.Payloads; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Neo.Plugins.OracleService +{ + interface IOracleProtocol : IDisposable + { + void Configure(); + Task<(OracleResponseCode, string)> ProcessAsync(Uri uri, CancellationToken cancellation); + } +} diff --git a/src/Plugins/OracleService/Protocols/OracleHttpsProtocol.cs b/src/Plugins/OracleService/Protocols/OracleHttpsProtocol.cs new file mode 100644 index 0000000000..a825f5e60f --- /dev/null +++ b/src/Plugins/OracleService/Protocols/OracleHttpsProtocol.cs @@ -0,0 +1,115 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// OracleHttpsProtocol.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Network.P2P.Payloads; +using System; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Neo.Plugins.OracleService +{ + class OracleHttpsProtocol : IOracleProtocol + { + private readonly HttpClient client = new(new HttpClientHandler() { AllowAutoRedirect = false }); + + public OracleHttpsProtocol() + { + CustomAttributeData attribute = Assembly.GetExecutingAssembly().CustomAttributes.First(p => p.AttributeType == typeof(AssemblyInformationalVersionAttribute)); + string version = (string)attribute.ConstructorArguments[0].Value; + client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("NeoOracleService", version)); + } + + public void Configure() + { + client.DefaultRequestHeaders.Accept.Clear(); + foreach (string type in Settings.Default.AllowedContentTypes) + client.DefaultRequestHeaders.Accept.ParseAdd(type); + client.Timeout = Settings.Default.Https.Timeout; + } + + public void Dispose() + { + client.Dispose(); + } + + public async Task<(OracleResponseCode, string)> ProcessAsync(Uri uri, CancellationToken cancellation) + { + Utility.Log(nameof(OracleHttpsProtocol), LogLevel.Debug, $"Request: {uri.AbsoluteUri}"); + + HttpResponseMessage message; + try + { + int redirects = 2; + do + { + if (!Settings.Default.AllowPrivateHost) + { + IPHostEntry entry = await Dns.GetHostEntryAsync(uri.Host, cancellation); + if (entry.IsInternal()) + return (OracleResponseCode.Forbidden, null); + } + message = await client.GetAsync(uri, HttpCompletionOption.ResponseContentRead, cancellation); + if (message.Headers.Location is not null) + { + uri = message.Headers.Location; + message = null; + } + } while (message == null && redirects-- > 0); + } + catch + { + return (OracleResponseCode.Timeout, null); + } + if (message is null) + return (OracleResponseCode.Timeout, null); + if (message.StatusCode == HttpStatusCode.NotFound) + return (OracleResponseCode.NotFound, null); + if (message.StatusCode == HttpStatusCode.Forbidden) + return (OracleResponseCode.Forbidden, null); + if (!message.IsSuccessStatusCode) + return (OracleResponseCode.Error, message.StatusCode.ToString()); + if (!Settings.Default.AllowedContentTypes.Contains(message.Content.Headers.ContentType.MediaType)) + return (OracleResponseCode.ContentTypeNotSupported, null); + if (message.Content.Headers.ContentLength.HasValue && message.Content.Headers.ContentLength > OracleResponse.MaxResultSize) + return (OracleResponseCode.ResponseTooLarge, null); + + byte[] buffer = new byte[OracleResponse.MaxResultSize + 1]; + var stream = message.Content.ReadAsStream(cancellation); + var read = await stream.ReadAsync(buffer, 0, buffer.Length, cancellation); + + if (read > OracleResponse.MaxResultSize) + return (OracleResponseCode.ResponseTooLarge, null); + + var encoding = GetEncoding(message.Content.Headers); + if (!encoding.Equals(Encoding.UTF8)) + return (OracleResponseCode.Error, null); + + return (OracleResponseCode.Success, Utility.StrictUTF8.GetString(buffer, 0, read)); + } + + private static Encoding GetEncoding(HttpContentHeaders headers) + { + Encoding encoding = null; + if ((headers.ContentType != null) && (headers.ContentType.CharSet != null)) + { + encoding = Encoding.GetEncoding(headers.ContentType.CharSet); + } + + return encoding ?? Encoding.UTF8; + } + } +} diff --git a/src/Plugins/OracleService/Protocols/OracleNeoFSProtocol.cs b/src/Plugins/OracleService/Protocols/OracleNeoFSProtocol.cs new file mode 100644 index 0000000000..de0cbd8044 --- /dev/null +++ b/src/Plugins/OracleService/Protocols/OracleNeoFSProtocol.cs @@ -0,0 +1,155 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// OracleNeoFSProtocol.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Cryptography.ECC; +using Neo.FileStorage.API.Client; +using Neo.FileStorage.API.Cryptography; +using Neo.FileStorage.API.Refs; +using Neo.Network.P2P.Payloads; +using Neo.Wallets; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Web; +using Object = Neo.FileStorage.API.Object.Object; +using Range = Neo.FileStorage.API.Object.Range; + +namespace Neo.Plugins.OracleService +{ + class OracleNeoFSProtocol : IOracleProtocol + { + private readonly System.Security.Cryptography.ECDsa privateKey; + + public OracleNeoFSProtocol(Wallet wallet, ECPoint[] oracles) + { + byte[] key = oracles.Select(p => wallet.GetAccount(p)).Where(p => p is not null && p.HasKey && !p.Lock).FirstOrDefault().GetKey().PrivateKey; + privateKey = key.LoadPrivateKey(); + } + + public void Configure() + { + } + + public void Dispose() + { + privateKey.Dispose(); + } + + public async Task<(OracleResponseCode, string)> ProcessAsync(Uri uri, CancellationToken cancellation) + { + Utility.Log(nameof(OracleNeoFSProtocol), LogLevel.Debug, $"Request: {uri.AbsoluteUri}"); + try + { + (OracleResponseCode code, string data) = await GetAsync(uri, Settings.Default.NeoFS.EndPoint, cancellation); + Utility.Log(nameof(OracleNeoFSProtocol), LogLevel.Debug, $"NeoFS result, code: {code}, data: {data}"); + return (code, data); + } + catch (Exception e) + { + Utility.Log(nameof(OracleNeoFSProtocol), LogLevel.Debug, $"NeoFS result: error,{e.Message}"); + return (OracleResponseCode.Error, null); + } + } + + + /// + /// GetAsync returns neofs object from the provided url. + /// If Command is not provided, full object is requested. + /// + /// URI scheme is "neofs:ContainerID/ObjectID/Command/offset|length". + /// Client host. + /// Cancellation token object. + /// Returns neofs object. + private async Task<(OracleResponseCode, string)> GetAsync(Uri uri, string host, CancellationToken cancellation) + { + string[] ps = uri.AbsolutePath.Split("/"); + if (ps.Length < 2) throw new FormatException("Invalid neofs url"); + ContainerID containerID = ContainerID.FromString(ps[0]); + ObjectID objectID = ObjectID.FromString(ps[1]); + Address objectAddr = new() + { + ContainerId = containerID, + ObjectId = objectID + }; + using Client client = new(privateKey, host); + var tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellation); + tokenSource.CancelAfter(Settings.Default.NeoFS.Timeout); + if (ps.Length == 2) + return GetPayload(client, objectAddr, tokenSource.Token); + return ps[2] switch + { + "range" => await GetRangeAsync(client, objectAddr, ps.Skip(3).ToArray(), tokenSource.Token), + "header" => (OracleResponseCode.Success, await GetHeaderAsync(client, objectAddr, tokenSource.Token)), + "hash" => (OracleResponseCode.Success, await GetHashAsync(client, objectAddr, ps.Skip(3).ToArray(), tokenSource.Token)), + _ => throw new Exception("invalid command") + }; + } + + private static (OracleResponseCode, string) GetPayload(Client client, Address addr, CancellationToken cancellation) + { + var objReader = client.GetObjectInit(addr, options: new CallOptions { Ttl = 2 }, context: cancellation); + var obj = objReader.ReadHeader(); + if (obj.PayloadSize > OracleResponse.MaxResultSize) + return (OracleResponseCode.ResponseTooLarge, ""); + var payload = new byte[obj.PayloadSize]; + int offset = 0; + while (true) + { + if ((ulong)offset > obj.PayloadSize) return (OracleResponseCode.ResponseTooLarge, ""); + (byte[] chunk, bool ok) = objReader.ReadChunk(); + if (!ok) break; + Array.Copy(chunk, 0, payload, offset, chunk.Length); + offset += chunk.Length; + } + return (OracleResponseCode.Success, Utility.StrictUTF8.GetString(payload)); + } + + private static async Task<(OracleResponseCode, string)> GetRangeAsync(Client client, Address addr, string[] ps, CancellationToken cancellation) + { + if (ps.Length == 0) throw new FormatException("missing object range (expected 'Offset|Length')"); + Range range = ParseRange(ps[0]); + if (range.Length > OracleResponse.MaxResultSize) return (OracleResponseCode.ResponseTooLarge, ""); + var res = await client.GetObjectPayloadRangeData(addr, range, options: new CallOptions { Ttl = 2 }, context: cancellation); + return (OracleResponseCode.Success, Utility.StrictUTF8.GetString(res)); + } + + private static async Task GetHeaderAsync(Client client, Address addr, CancellationToken cancellation) + { + var obj = await client.GetObjectHeader(addr, options: new CallOptions { Ttl = 2 }, context: cancellation); + return obj.ToString(); + } + + private static async Task GetHashAsync(Client client, Address addr, string[] ps, CancellationToken cancellation) + { + if (ps.Length == 0 || ps[0] == "") + { + Object obj = await client.GetObjectHeader(addr, options: new CallOptions { Ttl = 2 }, context: cancellation); + return $"\"{new UInt256(obj.PayloadChecksum.Sum.ToByteArray())}\""; + } + Range range = ParseRange(ps[0]); + List hashes = await client.GetObjectPayloadRangeHash(addr, new List() { range }, ChecksumType.Sha256, Array.Empty(), new CallOptions { Ttl = 2 }, cancellation); + if (hashes.Count == 0) throw new Exception("empty response, object range is invalid (expected 'Offset|Length')"); + return $"\"{new UInt256(hashes[0])}\""; + } + + private static Range ParseRange(string s) + { + string url = HttpUtility.UrlDecode(s); + int sepIndex = url.IndexOf("|"); + if (sepIndex < 0) throw new Exception("object range is invalid (expected 'Offset|Length')"); + ulong offset = ulong.Parse(url[..sepIndex]); + ulong length = ulong.Parse(url[(sepIndex + 1)..]); + return new Range() { Offset = offset, Length = length }; + } + } +} diff --git a/src/Plugins/OracleService/Settings.cs b/src/Plugins/OracleService/Settings.cs new file mode 100644 index 0000000000..952ea0c27b --- /dev/null +++ b/src/Plugins/OracleService/Settings.cs @@ -0,0 +1,72 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Settings.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.Extensions.Configuration; +using System; +using System.Linq; + +namespace Neo.Plugins.OracleService +{ + class HttpsSettings + { + public TimeSpan Timeout { get; } + + public HttpsSettings(IConfigurationSection section) + { + Timeout = TimeSpan.FromMilliseconds(section.GetValue("Timeout", 5000)); + } + } + + class NeoFSSettings + { + public string EndPoint { get; } + public TimeSpan Timeout { get; } + + public NeoFSSettings(IConfigurationSection section) + { + EndPoint = section.GetValue("EndPoint", "127.0.0.1:8080"); + Timeout = TimeSpan.FromMilliseconds(section.GetValue("Timeout", 15000)); + } + } + + class Settings + { + public uint Network { get; } + public Uri[] Nodes { get; } + public TimeSpan MaxTaskTimeout { get; } + public TimeSpan MaxOracleTimeout { get; } + public bool AllowPrivateHost { get; } + public string[] AllowedContentTypes { get; } + public HttpsSettings Https { get; } + public NeoFSSettings NeoFS { get; } + public bool AutoStart { get; } + + public static Settings Default { get; private set; } + + private Settings(IConfigurationSection section) + { + Network = section.GetValue("Network", 5195086u); + Nodes = section.GetSection("Nodes").GetChildren().Select(p => new Uri(p.Get(), UriKind.Absolute)).ToArray(); + MaxTaskTimeout = TimeSpan.FromMilliseconds(section.GetValue("MaxTaskTimeout", 432000000)); + MaxOracleTimeout = TimeSpan.FromMilliseconds(section.GetValue("MaxOracleTimeout", 15000)); + AllowPrivateHost = section.GetValue("AllowPrivateHost", false); + AllowedContentTypes = section.GetSection("AllowedContentTypes").GetChildren().Select(p => p.Get()).ToArray(); + Https = new HttpsSettings(section.GetSection("Https")); + NeoFS = new NeoFSSettings(section.GetSection("NeoFS")); + AutoStart = section.GetValue("AutoStart", false); + } + + public static void Load(IConfigurationSection section) + { + Default = new Settings(section); + } + } +} diff --git a/src/Plugins/RocksDBStore/Plugins/Storage/Options.cs b/src/Plugins/RocksDBStore/Plugins/Storage/Options.cs new file mode 100644 index 0000000000..26dd6c63aa --- /dev/null +++ b/src/Plugins/RocksDBStore/Plugins/Storage/Options.cs @@ -0,0 +1,36 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Options.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using RocksDbSharp; + +namespace Neo.Plugins.Storage +{ + public static class Options + { + public static readonly DbOptions Default = CreateDbOptions(); + public static readonly ReadOptions ReadDefault = new ReadOptions(); + public static readonly WriteOptions WriteDefault = new WriteOptions(); + public static readonly WriteOptions WriteDefaultSync = new WriteOptions().SetSync(true); + + public static DbOptions CreateDbOptions() + { + DbOptions options = new DbOptions(); + options.SetCreateMissingColumnFamilies(true); + options.SetCreateIfMissing(true); + options.SetErrorIfExists(false); + options.SetMaxOpenFiles(1000); + options.SetParanoidChecks(false); + options.SetWriteBufferSize(4 << 20); + options.SetBlockBasedTableFactory(new BlockBasedTableOptions().SetBlockSize(4096)); + return options; + } + } +} diff --git a/src/Plugins/RocksDBStore/Plugins/Storage/RocksDBStore.cs b/src/Plugins/RocksDBStore/Plugins/Storage/RocksDBStore.cs new file mode 100644 index 0000000000..079a012f67 --- /dev/null +++ b/src/Plugins/RocksDBStore/Plugins/Storage/RocksDBStore.cs @@ -0,0 +1,34 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RocksDBStore.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Persistence; + +namespace Neo.Plugins.Storage +{ + public class RocksDBStore : Plugin, IStoreProvider + { + public override string Description => "Uses RocksDBStore to store the blockchain data"; + + public RocksDBStore() + { + StoreFactory.RegisterProvider(this); + } + + /// + /// Get store + /// + /// RocksDbStore + public IStore GetStore(string path) + { + return new Store(path); + } + } +} diff --git a/src/Plugins/RocksDBStore/Plugins/Storage/Snapshot.cs b/src/Plugins/RocksDBStore/Plugins/Storage/Snapshot.cs new file mode 100644 index 0000000000..7423f6ae4a --- /dev/null +++ b/src/Plugins/RocksDBStore/Plugins/Storage/Snapshot.cs @@ -0,0 +1,82 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Snapshot.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Persistence; +using RocksDbSharp; +using System; +using System.Collections.Generic; + +namespace Neo.Plugins.Storage +{ + internal class Snapshot : ISnapshot + { + private readonly RocksDb db; + private readonly RocksDbSharp.Snapshot snapshot; + private readonly WriteBatch batch; + private readonly ReadOptions options; + + public Snapshot(RocksDb db) + { + this.db = db; + snapshot = db.CreateSnapshot(); + batch = new WriteBatch(); + + options = new ReadOptions(); + options.SetFillCache(false); + options.SetSnapshot(snapshot); + } + + public void Commit() + { + db.Write(batch, Options.WriteDefault); + } + + public void Delete(byte[] key) + { + batch.Delete(key); + } + + public void Put(byte[] key, byte[] value) + { + batch.Put(key, value); + } + + public IEnumerable<(byte[] Key, byte[] Value)> Seek(byte[] keyOrPrefix, SeekDirection direction) + { + if (keyOrPrefix == null) keyOrPrefix = Array.Empty(); + + using var it = db.NewIterator(readOptions: options); + + if (direction == SeekDirection.Forward) + for (it.Seek(keyOrPrefix); it.Valid(); it.Next()) + yield return (it.Key(), it.Value()); + else + for (it.SeekForPrev(keyOrPrefix); it.Valid(); it.Prev()) + yield return (it.Key(), it.Value()); + } + + public bool Contains(byte[] key) + { + return db.Get(key, Array.Empty(), 0, 0, readOptions: options) >= 0; + } + + public byte[] TryGet(byte[] key) + { + return db.Get(key, readOptions: options); + } + + public void Dispose() + { + snapshot.Dispose(); + batch.Dispose(); + } + } +} diff --git a/src/Plugins/RocksDBStore/Plugins/Storage/Store.cs b/src/Plugins/RocksDBStore/Plugins/Storage/Store.cs new file mode 100644 index 0000000000..ebf160dab0 --- /dev/null +++ b/src/Plugins/RocksDBStore/Plugins/Storage/Store.cs @@ -0,0 +1,77 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Store.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Persistence; +using RocksDbSharp; +using System; +using System.Collections.Generic; +using System.IO; + +namespace Neo.Plugins.Storage +{ + internal class Store : IStore + { + private readonly RocksDb db; + + public Store(string path) + { + db = RocksDb.Open(Options.Default, Path.GetFullPath(path)); + } + + public void Dispose() + { + db.Dispose(); + } + + public ISnapshot GetSnapshot() + { + return new Snapshot(db); + } + + public IEnumerable<(byte[] Key, byte[] Value)> Seek(byte[] keyOrPrefix, SeekDirection direction = SeekDirection.Forward) + { + if (keyOrPrefix == null) keyOrPrefix = Array.Empty(); + + using var it = db.NewIterator(); + if (direction == SeekDirection.Forward) + for (it.Seek(keyOrPrefix); it.Valid(); it.Next()) + yield return (it.Key(), it.Value()); + else + for (it.SeekForPrev(keyOrPrefix); it.Valid(); it.Prev()) + yield return (it.Key(), it.Value()); + } + + public bool Contains(byte[] key) + { + return db.Get(key, Array.Empty(), 0, 0) >= 0; + } + + public byte[] TryGet(byte[] key) + { + return db.Get(key); + } + + public void Delete(byte[] key) + { + db.Remove(key); + } + + public void Put(byte[] key, byte[] value) + { + db.Put(key, value); + } + + public void PutSync(byte[] key, byte[] value) + { + db.Put(key, value, writeOptions: Options.WriteDefaultSync); + } + } +} diff --git a/src/Plugins/RocksDBStore/RocksDBStore.csproj b/src/Plugins/RocksDBStore/RocksDBStore.csproj new file mode 100644 index 0000000000..d23f089160 --- /dev/null +++ b/src/Plugins/RocksDBStore/RocksDBStore.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + Neo.Plugins.Storage.RocksDBStore + Neo.Plugins.Storage + ../../../bin/$(PackageId) + + + + + + + diff --git a/src/Plugins/RpcClient/ContractClient.cs b/src/Plugins/RpcClient/ContractClient.cs new file mode 100644 index 0000000000..f4ec020abc --- /dev/null +++ b/src/Plugins/RpcClient/ContractClient.cs @@ -0,0 +1,77 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractClient.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Network.P2P.Payloads; +using Neo.Network.RPC.Models; +using Neo.SmartContract; +using Neo.SmartContract.Manifest; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using System.Threading.Tasks; + +namespace Neo.Network.RPC +{ + /// + /// Contract related operations through RPC API + /// + public class ContractClient + { + protected readonly RpcClient rpcClient; + + /// + /// ContractClient Constructor + /// + /// the RPC client to call NEO RPC methods + public ContractClient(RpcClient rpc) + { + rpcClient = rpc; + } + + /// + /// Use RPC method to test invoke operation. + /// + /// contract script hash + /// contract operation + /// operation arguments + /// + public Task TestInvokeAsync(UInt160 scriptHash, string operation, params object[] args) + { + byte[] script = scriptHash.MakeScript(operation, args); + return rpcClient.InvokeScriptAsync(script); + } + + /// + /// Deploy Contract, return signed transaction + /// + /// neo contract executable file + /// contract manifest + /// sender KeyPair + /// + public async Task CreateDeployContractTxAsync(byte[] nefFile, ContractManifest manifest, KeyPair key) + { + byte[] script; + using (ScriptBuilder sb = new ScriptBuilder()) + { + sb.EmitDynamicCall(NativeContract.ContractManagement.Hash, "deploy", nefFile, manifest.ToJson().ToString()); + script = sb.ToArray(); + } + UInt160 sender = Contract.CreateSignatureRedeemScript(key.PublicKey).ToScriptHash(); + Signer[] signers = new[] { new Signer { Scopes = WitnessScope.CalledByEntry, Account = sender } }; + + TransactionManagerFactory factory = new TransactionManagerFactory(rpcClient); + TransactionManager manager = await factory.MakeTransactionAsync(script, signers).ConfigureAwait(false); + return await manager + .AddSignature(key) + .SignAsync().ConfigureAwait(false); + } + } +} diff --git a/src/Plugins/RpcClient/Models/RpcAccount.cs b/src/Plugins/RpcClient/Models/RpcAccount.cs new file mode 100644 index 0000000000..2afe18b0e9 --- /dev/null +++ b/src/Plugins/RpcClient/Models/RpcAccount.cs @@ -0,0 +1,48 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcAccount.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; + +namespace Neo.Network.RPC.Models +{ + public class RpcAccount + { + public string Address { get; set; } + + public bool HasKey { get; set; } + + public string Label { get; set; } + + public bool WatchOnly { get; set; } + + public JObject ToJson() + { + return new JObject + { + ["address"] = Address, + ["haskey"] = HasKey, + ["label"] = Label, + ["watchonly"] = WatchOnly + }; + } + + public static RpcAccount FromJson(JObject json) + { + return new RpcAccount + { + Address = json["address"].AsString(), + HasKey = json["haskey"].AsBoolean(), + Label = json["label"]?.AsString(), + WatchOnly = json["watchonly"].AsBoolean(), + }; + } + } +} diff --git a/src/Plugins/RpcClient/Models/RpcApplicationLog.cs b/src/Plugins/RpcClient/Models/RpcApplicationLog.cs new file mode 100644 index 0000000000..b641f9df06 --- /dev/null +++ b/src/Plugins/RpcClient/Models/RpcApplicationLog.cs @@ -0,0 +1,118 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcApplicationLog.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using Neo.SmartContract; +using Neo.VM; +using Neo.VM.Types; +using System.Collections.Generic; +using System.Linq; + +namespace Neo.Network.RPC.Models +{ + public class RpcApplicationLog + { + public UInt256 TxId { get; set; } + + public UInt256 BlockHash { get; set; } + + public List Executions { get; set; } + + public JObject ToJson() + { + JObject json = new JObject(); + if (TxId != null) + json["txid"] = TxId.ToString(); + if (BlockHash != null) + json["blockhash"] = BlockHash.ToString(); + json["executions"] = Executions.Select(p => p.ToJson()).ToArray(); + return json; + } + + public static RpcApplicationLog FromJson(JObject json, ProtocolSettings protocolSettings) + { + return new RpcApplicationLog + { + TxId = json["txid"] is null ? null : UInt256.Parse(json["txid"].AsString()), + BlockHash = json["blockhash"] is null ? null : UInt256.Parse(json["blockhash"].AsString()), + Executions = ((JArray)json["executions"]).Select(p => Execution.FromJson((JObject)p, protocolSettings)).ToList(), + }; + } + } + + public class Execution + { + public TriggerType Trigger { get; set; } + + public VMState VMState { get; set; } + + public long GasConsumed { get; set; } + + public string ExceptionMessage { get; set; } + + public List Stack { get; set; } + + public List Notifications { get; set; } + + public JObject ToJson() + { + JObject json = new(); + json["trigger"] = Trigger; + json["vmstate"] = VMState; + json["gasconsumed"] = GasConsumed.ToString(); + json["exception"] = ExceptionMessage; + json["stack"] = Stack.Select(q => q.ToJson()).ToArray(); + json["notifications"] = Notifications.Select(q => q.ToJson()).ToArray(); + return json; + } + + public static Execution FromJson(JObject json, ProtocolSettings protocolSettings) + { + return new Execution + { + Trigger = json["trigger"].GetEnum(), + VMState = json["vmstate"].GetEnum(), + GasConsumed = long.Parse(json["gasconsumed"].AsString()), + ExceptionMessage = json["exception"]?.AsString(), + Stack = ((JArray)json["stack"]).Select(p => Utility.StackItemFromJson((JObject)p)).ToList(), + Notifications = ((JArray)json["notifications"]).Select(p => RpcNotifyEventArgs.FromJson((JObject)p, protocolSettings)).ToList() + }; + } + } + + public class RpcNotifyEventArgs + { + public UInt160 Contract { get; set; } + + public string EventName { get; set; } + + public StackItem State { get; set; } + + public JObject ToJson() + { + JObject json = new(); + json["contract"] = Contract.ToString(); + json["eventname"] = EventName; + json["state"] = State.ToJson(); + return json; + } + + public static RpcNotifyEventArgs FromJson(JObject json, ProtocolSettings protocolSettings) + { + return new RpcNotifyEventArgs + { + Contract = json["contract"].ToScriptHash(protocolSettings), + EventName = json["eventname"].AsString(), + State = Utility.StackItemFromJson((JObject)json["state"]) + }; + } + } +} diff --git a/src/Plugins/RpcClient/Models/RpcBlock.cs b/src/Plugins/RpcClient/Models/RpcBlock.cs new file mode 100644 index 0000000000..46f54a7230 --- /dev/null +++ b/src/Plugins/RpcClient/Models/RpcBlock.cs @@ -0,0 +1,43 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcBlock.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using Neo.Network.P2P.Payloads; + +namespace Neo.Network.RPC.Models +{ + public class RpcBlock + { + public Block Block { get; set; } + + public uint Confirmations { get; set; } + + public UInt256 NextBlockHash { get; set; } + + public JObject ToJson(ProtocolSettings protocolSettings) + { + JObject json = Utility.BlockToJson(Block, protocolSettings); + json["confirmations"] = Confirmations; + json["nextblockhash"] = NextBlockHash?.ToString(); + return json; + } + + public static RpcBlock FromJson(JObject json, ProtocolSettings protocolSettings) + { + return new RpcBlock + { + Block = Utility.BlockFromJson(json, protocolSettings), + Confirmations = (uint)json["confirmations"].AsNumber(), + NextBlockHash = json["nextblockhash"] is null ? null : UInt256.Parse(json["nextblockhash"].AsString()) + }; + } + } +} diff --git a/src/Plugins/RpcClient/Models/RpcBlockHeader.cs b/src/Plugins/RpcClient/Models/RpcBlockHeader.cs new file mode 100644 index 0000000000..e30a6a64a1 --- /dev/null +++ b/src/Plugins/RpcClient/Models/RpcBlockHeader.cs @@ -0,0 +1,43 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcBlockHeader.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using Neo.Network.P2P.Payloads; + +namespace Neo.Network.RPC.Models +{ + public class RpcBlockHeader + { + public Header Header { get; set; } + + public uint Confirmations { get; set; } + + public UInt256 NextBlockHash { get; set; } + + public JObject ToJson(ProtocolSettings protocolSettings) + { + JObject json = Header.ToJson(protocolSettings); + json["confirmations"] = Confirmations; + json["nextblockhash"] = NextBlockHash?.ToString(); + return json; + } + + public static RpcBlockHeader FromJson(JObject json, ProtocolSettings protocolSettings) + { + return new RpcBlockHeader + { + Header = Utility.HeaderFromJson(json, protocolSettings), + Confirmations = (uint)json["confirmations"].AsNumber(), + NextBlockHash = json["nextblockhash"] is null ? null : UInt256.Parse(json["nextblockhash"].AsString()) + }; + } + } +} diff --git a/src/Plugins/RpcClient/Models/RpcContractState.cs b/src/Plugins/RpcClient/Models/RpcContractState.cs new file mode 100644 index 0000000000..b77a2d3a89 --- /dev/null +++ b/src/Plugins/RpcClient/Models/RpcContractState.cs @@ -0,0 +1,42 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcContractState.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using Neo.SmartContract; +using Neo.SmartContract.Manifest; + +namespace Neo.Network.RPC.Models +{ + public class RpcContractState + { + public ContractState ContractState { get; set; } + + public JObject ToJson() + { + return ContractState.ToJson(); + } + + public static RpcContractState FromJson(JObject json) + { + return new RpcContractState + { + ContractState = new ContractState + { + Id = (int)json["id"].AsNumber(), + UpdateCounter = (ushort)json["updatecounter"].AsNumber(), + Hash = UInt160.Parse(json["hash"].AsString()), + Nef = RpcNefFile.FromJson((JObject)json["nef"]), + Manifest = ContractManifest.FromJson((JObject)json["manifest"]) + } + }; + } + } +} diff --git a/src/Plugins/RpcClient/Models/RpcFoundStates.cs b/src/Plugins/RpcClient/Models/RpcFoundStates.cs new file mode 100644 index 0000000000..a3a1c1f10a --- /dev/null +++ b/src/Plugins/RpcClient/Models/RpcFoundStates.cs @@ -0,0 +1,44 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcFoundStates.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using System; +using System.Linq; + +namespace Neo.Network.RPC.Models +{ + public class RpcFoundStates + { + public bool Truncated; + public (byte[] key, byte[] value)[] Results; + public byte[] FirstProof; + public byte[] LastProof; + + public static RpcFoundStates FromJson(JObject json) + { + return new RpcFoundStates + { + Truncated = json["truncated"].AsBoolean(), + Results = ((JArray)json["results"]) + .Select(j => ( + Convert.FromBase64String(j["key"].AsString()), + Convert.FromBase64String(j["value"].AsString()) + )) + .ToArray(), + FirstProof = ProofFromJson((JString)json["firstProof"]), + LastProof = ProofFromJson((JString)json["lastProof"]), + }; + } + + static byte[] ProofFromJson(JString json) + => json == null ? null : Convert.FromBase64String(json.AsString()); + } +} diff --git a/src/Plugins/RpcClient/Models/RpcInvokeResult.cs b/src/Plugins/RpcClient/Models/RpcInvokeResult.cs new file mode 100644 index 0000000000..6bd661e1ba --- /dev/null +++ b/src/Plugins/RpcClient/Models/RpcInvokeResult.cs @@ -0,0 +1,100 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcInvokeResult.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using Neo.VM; +using Neo.VM.Types; +using System; +using System.Linq; + +namespace Neo.Network.RPC.Models +{ + public class RpcInvokeResult + { + public string Script { get; set; } + + public VM.VMState State { get; set; } + + public long GasConsumed { get; set; } + + public StackItem[] Stack { get; set; } + + public string Tx { get; set; } + + public string Exception { get; set; } + + public string Session { get; set; } + + public JObject ToJson() + { + JObject json = new(); + json["script"] = Script; + json["state"] = State; + json["gasconsumed"] = GasConsumed.ToString(); + if (!string.IsNullOrEmpty(Exception)) + json["exception"] = Exception; + try + { + json["stack"] = new JArray(Stack.Select(p => p.ToJson())); + } + catch (InvalidOperationException) + { + // ContractParameter.ToJson() may cause InvalidOperationException + json["stack"] = "error: recursive reference"; + } + if (!string.IsNullOrEmpty(Tx)) json["tx"] = Tx; + return json; + } + + public static RpcInvokeResult FromJson(JObject json) + { + RpcInvokeResult invokeScriptResult = new() + { + Script = json["script"].AsString(), + State = json["state"].GetEnum(), + GasConsumed = long.Parse(json["gasconsumed"].AsString()), + Exception = json["exception"]?.AsString(), + Session = json["session"]?.AsString() + }; + try + { + invokeScriptResult.Stack = ((JArray)json["stack"]).Select(p => Utility.StackItemFromJson((JObject)p)).ToArray(); + } + catch { } + invokeScriptResult.Tx = json["tx"]?.AsString(); + return invokeScriptResult; + } + } + + public class RpcStack + { + public string Type { get; set; } + + public string Value { get; set; } + + public JObject ToJson() + { + JObject json = new(); + json["type"] = Type; + json["value"] = Value; + return json; + } + + public static RpcStack FromJson(JObject json) + { + return new RpcStack + { + Type = json["type"].AsString(), + Value = json["value"].AsString() + }; + } + } +} diff --git a/src/Plugins/RpcClient/Models/RpcMethodToken.cs b/src/Plugins/RpcClient/Models/RpcMethodToken.cs new file mode 100644 index 0000000000..f426950deb --- /dev/null +++ b/src/Plugins/RpcClient/Models/RpcMethodToken.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcMethodToken.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using Neo.SmartContract; +using System; + +namespace Neo.Network.RPC.Models +{ + class RpcMethodToken + { + public static MethodToken FromJson(JObject json) + { + return new MethodToken + { + Hash = UInt160.Parse(json["hash"].AsString()), + Method = json["method"].AsString(), + ParametersCount = (ushort)json["paramcount"].AsNumber(), + HasReturnValue = json["hasreturnvalue"].AsBoolean(), + CallFlags = (CallFlags)Enum.Parse(typeof(CallFlags), json["callflags"].AsString()) + }; + } + } +} diff --git a/src/Plugins/RpcClient/Models/RpcNefFile.cs b/src/Plugins/RpcClient/Models/RpcNefFile.cs new file mode 100644 index 0000000000..4b33f2b6ac --- /dev/null +++ b/src/Plugins/RpcClient/Models/RpcNefFile.cs @@ -0,0 +1,33 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcNefFile.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using Neo.SmartContract; +using System; +using System.Linq; + +namespace Neo.Network.RPC.Models +{ + class RpcNefFile + { + public static NefFile FromJson(JObject json) + { + return new NefFile + { + Compiler = json["compiler"].AsString(), + Source = json["source"].AsString(), + Tokens = ((JArray)json["tokens"]).Select(p => RpcMethodToken.FromJson((JObject)p)).ToArray(), + Script = Convert.FromBase64String(json["script"].AsString()), + CheckSum = (uint)json["checksum"].AsNumber() + }; + } + } +} diff --git a/src/Plugins/RpcClient/Models/RpcNep17Balances.cs b/src/Plugins/RpcClient/Models/RpcNep17Balances.cs new file mode 100644 index 0000000000..f7f8b00dbe --- /dev/null +++ b/src/Plugins/RpcClient/Models/RpcNep17Balances.cs @@ -0,0 +1,73 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcNep17Balances.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using Neo.Wallets; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; + +namespace Neo.Network.RPC.Models +{ + public class RpcNep17Balances + { + public UInt160 UserScriptHash { get; set; } + + public List Balances { get; set; } + + public JObject ToJson(ProtocolSettings protocolSettings) + { + JObject json = new(); + json["balance"] = Balances.Select(p => p.ToJson()).ToArray(); + json["address"] = UserScriptHash.ToAddress(protocolSettings.AddressVersion); + return json; + } + + public static RpcNep17Balances FromJson(JObject json, ProtocolSettings protocolSettings) + { + RpcNep17Balances nep17Balance = new() + { + Balances = ((JArray)json["balance"]).Select(p => RpcNep17Balance.FromJson((JObject)p, protocolSettings)).ToList(), + UserScriptHash = json["address"].ToScriptHash(protocolSettings) + }; + return nep17Balance; + } + } + + public class RpcNep17Balance + { + public UInt160 AssetHash { get; set; } + + public BigInteger Amount { get; set; } + + public uint LastUpdatedBlock { get; set; } + + public JObject ToJson() + { + JObject json = new(); + json["assethash"] = AssetHash.ToString(); + json["amount"] = Amount.ToString(); + json["lastupdatedblock"] = LastUpdatedBlock; + return json; + } + + public static RpcNep17Balance FromJson(JObject json, ProtocolSettings protocolSettings) + { + RpcNep17Balance balance = new() + { + AssetHash = json["assethash"].ToScriptHash(protocolSettings), + Amount = BigInteger.Parse(json["amount"].AsString()), + LastUpdatedBlock = (uint)json["lastupdatedblock"].AsNumber() + }; + return balance; + } + } +} diff --git a/src/Plugins/RpcClient/Models/RpcNep17TokenInfo.cs b/src/Plugins/RpcClient/Models/RpcNep17TokenInfo.cs new file mode 100644 index 0000000000..a7cb6d0ef4 --- /dev/null +++ b/src/Plugins/RpcClient/Models/RpcNep17TokenInfo.cs @@ -0,0 +1,26 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcNep17TokenInfo.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Numerics; + +namespace Neo.Network.RPC.Models +{ + public class RpcNep17TokenInfo + { + public string Name { get; set; } + + public string Symbol { get; set; } + + public byte Decimals { get; set; } + + public BigInteger TotalSupply { get; set; } + } +} diff --git a/src/Plugins/RpcClient/Models/RpcNep17Transfers.cs b/src/Plugins/RpcClient/Models/RpcNep17Transfers.cs new file mode 100644 index 0000000000..3a3226b9a9 --- /dev/null +++ b/src/Plugins/RpcClient/Models/RpcNep17Transfers.cs @@ -0,0 +1,92 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcNep17Transfers.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using Neo.Wallets; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; + +namespace Neo.Network.RPC.Models +{ + public class RpcNep17Transfers + { + public UInt160 UserScriptHash { get; set; } + + public List Sent { get; set; } + + public List Received { get; set; } + + public JObject ToJson(ProtocolSettings protocolSettings) + { + JObject json = new(); + json["sent"] = Sent.Select(p => p.ToJson(protocolSettings)).ToArray(); + json["received"] = Received.Select(p => p.ToJson(protocolSettings)).ToArray(); + json["address"] = UserScriptHash.ToAddress(protocolSettings.AddressVersion); + return json; + } + + public static RpcNep17Transfers FromJson(JObject json, ProtocolSettings protocolSettings) + { + RpcNep17Transfers transfers = new() + { + Sent = ((JArray)json["sent"]).Select(p => RpcNep17Transfer.FromJson((JObject)p, protocolSettings)).ToList(), + Received = ((JArray)json["received"]).Select(p => RpcNep17Transfer.FromJson((JObject)p, protocolSettings)).ToList(), + UserScriptHash = json["address"].ToScriptHash(protocolSettings) + }; + return transfers; + } + } + + public class RpcNep17Transfer + { + public ulong TimestampMS { get; set; } + + public UInt160 AssetHash { get; set; } + + public UInt160 UserScriptHash { get; set; } + + public BigInteger Amount { get; set; } + + public uint BlockIndex { get; set; } + + public ushort TransferNotifyIndex { get; set; } + + public UInt256 TxHash { get; set; } + + public JObject ToJson(ProtocolSettings protocolSettings) + { + JObject json = new(); + json["timestamp"] = TimestampMS; + json["assethash"] = AssetHash.ToString(); + json["transferaddress"] = UserScriptHash?.ToAddress(protocolSettings.AddressVersion); + json["amount"] = Amount.ToString(); + json["blockindex"] = BlockIndex; + json["transfernotifyindex"] = TransferNotifyIndex; + json["txhash"] = TxHash.ToString(); + return json; + } + + public static RpcNep17Transfer FromJson(JObject json, ProtocolSettings protocolSettings) + { + return new RpcNep17Transfer + { + TimestampMS = (ulong)json["timestamp"].AsNumber(), + AssetHash = json["assethash"].ToScriptHash(protocolSettings), + UserScriptHash = json["transferaddress"]?.ToScriptHash(protocolSettings), + Amount = BigInteger.Parse(json["amount"].AsString()), + BlockIndex = (uint)json["blockindex"].AsNumber(), + TransferNotifyIndex = (ushort)json["transfernotifyindex"].AsNumber(), + TxHash = UInt256.Parse(json["txhash"].AsString()) + }; + } + } +} diff --git a/src/Plugins/RpcClient/Models/RpcPeers.cs b/src/Plugins/RpcClient/Models/RpcPeers.cs new file mode 100644 index 0000000000..6659ffe0dd --- /dev/null +++ b/src/Plugins/RpcClient/Models/RpcPeers.cs @@ -0,0 +1,68 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcPeers.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using System.Linq; + +namespace Neo.Network.RPC.Models +{ + public class RpcPeers + { + public RpcPeer[] Unconnected { get; set; } + + public RpcPeer[] Bad { get; set; } + + public RpcPeer[] Connected { get; set; } + + public JObject ToJson() + { + JObject json = new(); + json["unconnected"] = new JArray(Unconnected.Select(p => p.ToJson())); + json["bad"] = new JArray(Bad.Select(p => p.ToJson())); + json["connected"] = new JArray(Connected.Select(p => p.ToJson())); + return json; + } + + public static RpcPeers FromJson(JObject json) + { + return new RpcPeers + { + Unconnected = ((JArray)json["unconnected"]).Select(p => RpcPeer.FromJson((JObject)p)).ToArray(), + Bad = ((JArray)json["bad"]).Select(p => RpcPeer.FromJson((JObject)p)).ToArray(), + Connected = ((JArray)json["connected"]).Select(p => RpcPeer.FromJson((JObject)p)).ToArray() + }; + } + } + + public class RpcPeer + { + public string Address { get; set; } + + public int Port { get; set; } + + public JObject ToJson() + { + JObject json = new(); + json["address"] = Address; + json["port"] = Port; + return json; + } + + public static RpcPeer FromJson(JObject json) + { + return new RpcPeer + { + Address = json["address"].AsString(), + Port = int.Parse(json["port"].AsString()) + }; + } + } +} diff --git a/src/Plugins/RpcClient/Models/RpcPlugin.cs b/src/Plugins/RpcClient/Models/RpcPlugin.cs new file mode 100644 index 0000000000..12a8669dea --- /dev/null +++ b/src/Plugins/RpcClient/Models/RpcPlugin.cs @@ -0,0 +1,44 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcPlugin.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using System.Linq; + +namespace Neo.Network.RPC.Models +{ + public class RpcPlugin + { + public string Name { get; set; } + + public string Version { get; set; } + + public string[] Interfaces { get; set; } + + public JObject ToJson() + { + JObject json = new(); + json["name"] = Name; + json["version"] = Version; + json["interfaces"] = new JArray(Interfaces.Select(p => (JToken)p)); + return json; + } + + public static RpcPlugin FromJson(JObject json) + { + return new RpcPlugin + { + Name = json["name"].AsString(), + Version = json["version"].AsString(), + Interfaces = ((JArray)json["interfaces"]).Select(p => p.AsString()).ToArray() + }; + } + } +} diff --git a/src/Plugins/RpcClient/Models/RpcRawMemPool.cs b/src/Plugins/RpcClient/Models/RpcRawMemPool.cs new file mode 100644 index 0000000000..4474e0b6be --- /dev/null +++ b/src/Plugins/RpcClient/Models/RpcRawMemPool.cs @@ -0,0 +1,45 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcRawMemPool.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using System.Collections.Generic; +using System.Linq; + +namespace Neo.Network.RPC.Models +{ + public class RpcRawMemPool + { + public uint Height { get; set; } + + public List Verified { get; set; } + + public List UnVerified { get; set; } + + public JObject ToJson() + { + JObject json = new(); + json["height"] = Height; + json["verified"] = new JArray(Verified.Select(p => (JToken)p.ToString())); + json["unverified"] = new JArray(UnVerified.Select(p => (JToken)p.ToString())); + return json; + } + + public static RpcRawMemPool FromJson(JObject json) + { + return new RpcRawMemPool + { + Height = uint.Parse(json["height"].AsString()), + Verified = ((JArray)json["verified"]).Select(p => UInt256.Parse(p.AsString())).ToList(), + UnVerified = ((JArray)json["unverified"]).Select(p => UInt256.Parse(p.AsString())).ToList() + }; + } + } +} diff --git a/src/Plugins/RpcClient/Models/RpcRequest.cs b/src/Plugins/RpcClient/Models/RpcRequest.cs new file mode 100644 index 0000000000..1c4b67415a --- /dev/null +++ b/src/Plugins/RpcClient/Models/RpcRequest.cs @@ -0,0 +1,48 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcRequest.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using System.Linq; + +namespace Neo.Network.RPC.Models +{ + public class RpcRequest + { + public JToken Id { get; set; } + + public string JsonRpc { get; set; } + + public string Method { get; set; } + + public JToken[] Params { get; set; } + + public static RpcRequest FromJson(JObject json) + { + return new RpcRequest + { + Id = json["id"], + JsonRpc = json["jsonrpc"].AsString(), + Method = json["method"].AsString(), + Params = ((JArray)json["params"]).ToArray() + }; + } + + public JObject ToJson() + { + var json = new JObject(); + json["id"] = Id; + json["jsonrpc"] = JsonRpc; + json["method"] = Method; + json["params"] = new JArray(Params); + return json; + } + } +} diff --git a/src/Plugins/RpcClient/Models/RpcResponse.cs b/src/Plugins/RpcClient/Models/RpcResponse.cs new file mode 100644 index 0000000000..25e3212fc3 --- /dev/null +++ b/src/Plugins/RpcClient/Models/RpcResponse.cs @@ -0,0 +1,83 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcResponse.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; + +namespace Neo.Network.RPC.Models +{ + public class RpcResponse + { + public JToken Id { get; set; } + + public string JsonRpc { get; set; } + + public RpcResponseError Error { get; set; } + + public JToken Result { get; set; } + + public string RawResponse { get; set; } + + public static RpcResponse FromJson(JObject json) + { + RpcResponse response = new() + { + Id = json["id"], + JsonRpc = json["jsonrpc"].AsString(), + Result = json["result"] + }; + + if (json["error"] != null) + { + response.Error = RpcResponseError.FromJson((JObject)json["error"]); + } + + return response; + } + + public JObject ToJson() + { + JObject json = new(); + json["id"] = Id; + json["jsonrpc"] = JsonRpc; + json["error"] = Error?.ToJson(); + json["result"] = Result; + return json; + } + } + + public class RpcResponseError + { + public int Code { get; set; } + + public string Message { get; set; } + + public JToken Data { get; set; } + + public static RpcResponseError FromJson(JObject json) + { + return new RpcResponseError + { + Code = (int)json["code"].AsNumber(), + Message = json["message"].AsString(), + Data = json["data"], + }; + } + + public JObject ToJson() + { + JObject json = new(); + json["code"] = Code; + json["message"] = Message; + json["data"] = Data; + return json; + } + } +} diff --git a/src/Plugins/RpcClient/Models/RpcStateRoot.cs b/src/Plugins/RpcClient/Models/RpcStateRoot.cs new file mode 100644 index 0000000000..095b054a33 --- /dev/null +++ b/src/Plugins/RpcClient/Models/RpcStateRoot.cs @@ -0,0 +1,36 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcStateRoot.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using Neo.Network.P2P.Payloads; +using System.Linq; + +namespace Neo.Network.RPC.Models +{ + public class RpcStateRoot + { + public byte Version; + public uint Index; + public UInt256 RootHash; + public Witness Witness; + + public static RpcStateRoot FromJson(JObject json) + { + return new RpcStateRoot + { + Version = (byte)json["version"].AsNumber(), + Index = (uint)json["index"].AsNumber(), + RootHash = UInt256.Parse(json["roothash"].AsString()), + Witness = ((JArray)json["witnesses"]).Select(p => Utility.WitnessFromJson((JObject)p)).FirstOrDefault() + }; + } + } +} diff --git a/src/Plugins/RpcClient/Models/RpcTransaction.cs b/src/Plugins/RpcClient/Models/RpcTransaction.cs new file mode 100644 index 0000000000..cb674316d5 --- /dev/null +++ b/src/Plugins/RpcClient/Models/RpcTransaction.cs @@ -0,0 +1,62 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcTransaction.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using Neo.Network.P2P.Payloads; +using Neo.VM; + +namespace Neo.Network.RPC.Models +{ + public class RpcTransaction + { + public Transaction Transaction { get; set; } + + public UInt256 BlockHash { get; set; } + + public uint? Confirmations { get; set; } + + public ulong? BlockTime { get; set; } + + public VMState? VMState { get; set; } + + public JObject ToJson(ProtocolSettings protocolSettings) + { + JObject json = Utility.TransactionToJson(Transaction, protocolSettings); + if (Confirmations != null) + { + json["blockhash"] = BlockHash.ToString(); + json["confirmations"] = Confirmations; + json["blocktime"] = BlockTime; + if (VMState != null) + { + json["vmstate"] = VMState; + } + } + return json; + } + + public static RpcTransaction FromJson(JObject json, ProtocolSettings protocolSettings) + { + RpcTransaction transaction = new RpcTransaction + { + Transaction = Utility.TransactionFromJson(json, protocolSettings) + }; + if (json["confirmations"] != null) + { + transaction.BlockHash = UInt256.Parse(json["blockhash"].AsString()); + transaction.Confirmations = (uint)json["confirmations"].AsNumber(); + transaction.BlockTime = (ulong)json["blocktime"].AsNumber(); + transaction.VMState = json["vmstate"]?.GetEnum(); + } + return transaction; + } + } +} diff --git a/src/Plugins/RpcClient/Models/RpcTransferOut.cs b/src/Plugins/RpcClient/Models/RpcTransferOut.cs new file mode 100644 index 0000000000..d5e82a8468 --- /dev/null +++ b/src/Plugins/RpcClient/Models/RpcTransferOut.cs @@ -0,0 +1,45 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcTransferOut.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using Neo.Wallets; + +namespace Neo.Network.RPC.Models +{ + public class RpcTransferOut + { + public UInt160 Asset { get; set; } + + public UInt160 ScriptHash { get; set; } + + public string Value { get; set; } + + public JObject ToJson(ProtocolSettings protocolSettings) + { + return new JObject + { + ["asset"] = Asset.ToString(), + ["value"] = Value, + ["address"] = ScriptHash.ToAddress(protocolSettings.AddressVersion), + }; + } + + public static RpcTransferOut FromJson(JObject json, ProtocolSettings protocolSettings) + { + return new RpcTransferOut + { + Asset = json["asset"].ToScriptHash(protocolSettings), + Value = json["value"].AsString(), + ScriptHash = json["address"].ToScriptHash(protocolSettings), + }; + } + } +} diff --git a/src/Plugins/RpcClient/Models/RpcUnclaimedGas.cs b/src/Plugins/RpcClient/Models/RpcUnclaimedGas.cs new file mode 100644 index 0000000000..c25f527d33 --- /dev/null +++ b/src/Plugins/RpcClient/Models/RpcUnclaimedGas.cs @@ -0,0 +1,39 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcUnclaimedGas.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; + +namespace Neo.Network.RPC.Models +{ + public class RpcUnclaimedGas + { + public long Unclaimed { get; set; } + + public string Address { get; set; } + + public JObject ToJson() + { + JObject json = new(); + json["unclaimed"] = Unclaimed.ToString(); + json["address"] = Address; + return json; + } + + public static RpcUnclaimedGas FromJson(JObject json) + { + return new RpcUnclaimedGas + { + Unclaimed = long.Parse(json["unclaimed"].AsString()), + Address = json["address"].AsString() + }; + } + } +} diff --git a/src/Plugins/RpcClient/Models/RpcValidateAddressResult.cs b/src/Plugins/RpcClient/Models/RpcValidateAddressResult.cs new file mode 100644 index 0000000000..6f49e08f06 --- /dev/null +++ b/src/Plugins/RpcClient/Models/RpcValidateAddressResult.cs @@ -0,0 +1,39 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcValidateAddressResult.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; + +namespace Neo.Network.RPC.Models +{ + public class RpcValidateAddressResult + { + public string Address { get; set; } + + public bool IsValid { get; set; } + + public JObject ToJson() + { + JObject json = new(); + json["address"] = Address; + json["isvalid"] = IsValid; + return json; + } + + public static RpcValidateAddressResult FromJson(JObject json) + { + return new RpcValidateAddressResult + { + Address = json["address"].AsString(), + IsValid = json["isvalid"].AsBoolean() + }; + } + } +} diff --git a/src/Plugins/RpcClient/Models/RpcValidator.cs b/src/Plugins/RpcClient/Models/RpcValidator.cs new file mode 100644 index 0000000000..27031631e9 --- /dev/null +++ b/src/Plugins/RpcClient/Models/RpcValidator.cs @@ -0,0 +1,40 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcValidator.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using System.Numerics; + +namespace Neo.Network.RPC.Models +{ + public class RpcValidator + { + public string PublicKey { get; set; } + + public BigInteger Votes { get; set; } + + public JObject ToJson() + { + JObject json = new(); + json["publickey"] = PublicKey; + json["votes"] = Votes.ToString(); + return json; + } + + public static RpcValidator FromJson(JObject json) + { + return new RpcValidator + { + PublicKey = json["publickey"].AsString(), + Votes = BigInteger.Parse(json["votes"].AsString()), + }; + } + } +} diff --git a/src/Plugins/RpcClient/Models/RpcVersion.cs b/src/Plugins/RpcClient/Models/RpcVersion.cs new file mode 100644 index 0000000000..430d659f7c --- /dev/null +++ b/src/Plugins/RpcClient/Models/RpcVersion.cs @@ -0,0 +1,113 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcVersion.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Neo.Network.RPC.Models +{ + public class RpcVersion + { + public class RpcProtocol + { + public uint Network { get; set; } + public int ValidatorsCount { get; set; } + public uint MillisecondsPerBlock { get; set; } + public uint MaxValidUntilBlockIncrement { get; set; } + public uint MaxTraceableBlocks { get; set; } + public byte AddressVersion { get; set; } + public uint MaxTransactionsPerBlock { get; set; } + public int MemoryPoolMaxTransactions { get; set; } + public ulong InitialGasDistribution { get; set; } + public IReadOnlyDictionary Hardforks { get; set; } + + public JObject ToJson() + { + JObject json = new(); + json["network"] = Network; + json["validatorscount"] = ValidatorsCount; + json["msperblock"] = MillisecondsPerBlock; + json["maxvaliduntilblockincrement"] = MaxValidUntilBlockIncrement; + json["maxtraceableblocks"] = MaxTraceableBlocks; + json["addressversion"] = AddressVersion; + json["maxtransactionsperblock"] = MaxTransactionsPerBlock; + json["memorypoolmaxtransactions"] = MemoryPoolMaxTransactions; + json["initialgasdistribution"] = InitialGasDistribution; + json["hardforks"] = new JArray(Hardforks.Select(s => new JObject() + { + // Strip HF_ prefix. + ["name"] = StripPrefix(s.Key.ToString(), "HF_"), + ["blockheight"] = s.Value, + })); + return json; + } + + public static RpcProtocol FromJson(JObject json) + { + return new() + { + Network = (uint)json["network"].AsNumber(), + ValidatorsCount = (int)json["validatorscount"].AsNumber(), + MillisecondsPerBlock = (uint)json["msperblock"].AsNumber(), + MaxValidUntilBlockIncrement = (uint)json["maxvaliduntilblockincrement"].AsNumber(), + MaxTraceableBlocks = (uint)json["maxtraceableblocks"].AsNumber(), + AddressVersion = (byte)json["addressversion"].AsNumber(), + MaxTransactionsPerBlock = (uint)json["maxtransactionsperblock"].AsNumber(), + MemoryPoolMaxTransactions = (int)json["memorypoolmaxtransactions"].AsNumber(), + InitialGasDistribution = (ulong)json["initialgasdistribution"].AsNumber(), + Hardforks = new Dictionary(((JArray)json["hardforks"]).Select(s => + { + var name = s["name"].AsString(); + // Add HF_ prefix to the hardfork response for proper Hardfork enum parsing. + return new KeyValuePair(Enum.Parse(name.StartsWith("HF_") ? name : $"HF_{name}"), (uint)s["blockheight"].AsNumber()); + })), + }; + } + + private static string StripPrefix(string s, string prefix) + { + return s.StartsWith(prefix) ? s.Substring(prefix.Length) : s; + } + } + + public int TcpPort { get; set; } + + public uint Nonce { get; set; } + + public string UserAgent { get; set; } + + public RpcProtocol Protocol { get; set; } = new(); + + public JObject ToJson() + { + JObject json = new(); + json["network"] = Protocol.Network; // Obsolete + json["tcpport"] = TcpPort; + json["nonce"] = Nonce; + json["useragent"] = UserAgent; + json["protocol"] = Protocol.ToJson(); + return json; + } + + public static RpcVersion FromJson(JObject json) + { + return new() + { + TcpPort = (int)json["tcpport"].AsNumber(), + Nonce = (uint)json["nonce"].AsNumber(), + UserAgent = json["useragent"].AsString(), + Protocol = RpcProtocol.FromJson((JObject)json["protocol"]) + }; + } + } +} diff --git a/src/Plugins/RpcClient/Nep17API.cs b/src/Plugins/RpcClient/Nep17API.cs new file mode 100644 index 0000000000..518f470924 --- /dev/null +++ b/src/Plugins/RpcClient/Nep17API.cs @@ -0,0 +1,182 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Nep17API.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Cryptography.ECC; +using Neo.Network.P2P.Payloads; +using Neo.Network.RPC.Models; +using Neo.SmartContract; +using Neo.VM; +using Neo.Wallets; +using System; +using System.Linq; +using System.Numerics; +using System.Threading.Tasks; +using static Neo.Helper; + +namespace Neo.Network.RPC +{ + /// + /// Call NEP17 methods with RPC API + /// + public class Nep17API : ContractClient + { + /// + /// Nep17API Constructor + /// + /// the RPC client to call NEO RPC methods + public Nep17API(RpcClient rpcClient) : base(rpcClient) { } + + /// + /// Get balance of NEP17 token + /// + /// contract script hash + /// account script hash + /// + public async Task BalanceOfAsync(UInt160 scriptHash, UInt160 account) + { + var result = await TestInvokeAsync(scriptHash, "balanceOf", account).ConfigureAwait(false); + BigInteger balance = result.Stack.Single().GetInteger(); + return balance; + } + + /// + /// Get symbol of NEP17 token + /// + /// contract script hash + /// + public async Task SymbolAsync(UInt160 scriptHash) + { + var result = await TestInvokeAsync(scriptHash, "symbol").ConfigureAwait(false); + return result.Stack.Single().GetString(); + } + + /// + /// Get decimals of NEP17 token + /// + /// contract script hash + /// + public async Task DecimalsAsync(UInt160 scriptHash) + { + var result = await TestInvokeAsync(scriptHash, "decimals").ConfigureAwait(false); + return (byte)result.Stack.Single().GetInteger(); + } + + /// + /// Get total supply of NEP17 token + /// + /// contract script hash + /// + public async Task TotalSupplyAsync(UInt160 scriptHash) + { + var result = await TestInvokeAsync(scriptHash, "totalSupply").ConfigureAwait(false); + return result.Stack.Single().GetInteger(); + } + + /// + /// Get token information in one rpc call + /// + /// contract script hash + /// + public async Task GetTokenInfoAsync(UInt160 scriptHash) + { + var contractState = await rpcClient.GetContractStateAsync(scriptHash.ToString()).ConfigureAwait(false); + byte[] script = Concat( + scriptHash.MakeScript("symbol"), + scriptHash.MakeScript("decimals"), + scriptHash.MakeScript("totalSupply")); + var name = contractState.Manifest.Name; + var result = await rpcClient.InvokeScriptAsync(script).ConfigureAwait(false); + var stack = result.Stack; + + return new RpcNep17TokenInfo + { + Name = name, + Symbol = stack[0].GetString(), + Decimals = (byte)stack[1].GetInteger(), + TotalSupply = stack[2].GetInteger() + }; + } + + public async Task GetTokenInfoAsync(string contractHash) + { + var contractState = await rpcClient.GetContractStateAsync(contractHash).ConfigureAwait(false); + byte[] script = Concat( + contractState.Hash.MakeScript("symbol"), + contractState.Hash.MakeScript("decimals"), + contractState.Hash.MakeScript("totalSupply")); + var name = contractState.Manifest.Name; + var result = await rpcClient.InvokeScriptAsync(script).ConfigureAwait(false); + var stack = result.Stack; + + return new RpcNep17TokenInfo + { + Name = name, + Symbol = stack[0].GetString(), + Decimals = (byte)stack[1].GetInteger(), + TotalSupply = stack[2].GetInteger() + }; + } + + /// + /// Create NEP17 token transfer transaction + /// + /// contract script hash + /// from KeyPair + /// to account script hash + /// transfer amount + /// onPayment data + /// Add assert at the end of the script + /// + public async Task CreateTransferTxAsync(UInt160 scriptHash, KeyPair fromKey, UInt160 to, BigInteger amount, object data = null, bool addAssert = true) + { + var sender = Contract.CreateSignatureRedeemScript(fromKey.PublicKey).ToScriptHash(); + Signer[] signers = new[] { new Signer { Scopes = WitnessScope.CalledByEntry, Account = sender } }; + byte[] script = scriptHash.MakeScript("transfer", sender, to, amount, data); + if (addAssert) script = script.Concat(new[] { (byte)OpCode.ASSERT }).ToArray(); + + TransactionManagerFactory factory = new(rpcClient); + TransactionManager manager = await factory.MakeTransactionAsync(script, signers).ConfigureAwait(false); + + return await manager + .AddSignature(fromKey) + .SignAsync().ConfigureAwait(false); + } + + /// + /// Create NEP17 token transfer transaction from multi-sig account + /// + /// contract script hash + /// multi-sig min signature count + /// multi-sig pubKeys + /// sign keys + /// to account + /// transfer amount + /// onPayment data + /// Add assert at the end of the script + /// + public async Task CreateTransferTxAsync(UInt160 scriptHash, int m, ECPoint[] pubKeys, KeyPair[] fromKeys, UInt160 to, BigInteger amount, object data = null, bool addAssert = true) + { + if (m > fromKeys.Length) + throw new ArgumentException($"Need at least {m} KeyPairs for signing!"); + var sender = Contract.CreateMultiSigContract(m, pubKeys).ScriptHash; + Signer[] signers = new[] { new Signer { Scopes = WitnessScope.CalledByEntry, Account = sender } }; + byte[] script = scriptHash.MakeScript("transfer", sender, to, amount, data); + if (addAssert) script = script.Concat(new[] { (byte)OpCode.ASSERT }).ToArray(); + + TransactionManagerFactory factory = new(rpcClient); + TransactionManager manager = await factory.MakeTransactionAsync(script, signers).ConfigureAwait(false); + + return await manager + .AddMultiSig(fromKeys, m, pubKeys) + .SignAsync().ConfigureAwait(false); + } + } +} diff --git a/src/Plugins/RpcClient/PolicyAPI.cs b/src/Plugins/RpcClient/PolicyAPI.cs new file mode 100644 index 0000000000..60e749a79c --- /dev/null +++ b/src/Plugins/RpcClient/PolicyAPI.cs @@ -0,0 +1,71 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// PolicyAPI.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.SmartContract.Native; +using System.Linq; +using System.Threading.Tasks; + +namespace Neo.Network.RPC +{ + /// + /// Get Policy info by RPC API + /// + public class PolicyAPI : ContractClient + { + readonly UInt160 scriptHash = NativeContract.Policy.Hash; + + /// + /// PolicyAPI Constructor + /// + /// the RPC client to call NEO RPC methods + public PolicyAPI(RpcClient rpcClient) : base(rpcClient) { } + + /// + /// Get Fee Factor + /// + /// + public async Task GetExecFeeFactorAsync() + { + var result = await TestInvokeAsync(scriptHash, "getExecFeeFactor").ConfigureAwait(false); + return (uint)result.Stack.Single().GetInteger(); + } + + /// + /// Get Storage Price + /// + /// + public async Task GetStoragePriceAsync() + { + var result = await TestInvokeAsync(scriptHash, "getStoragePrice").ConfigureAwait(false); + return (uint)result.Stack.Single().GetInteger(); + } + + /// + /// Get Network Fee Per Byte + /// + /// + public async Task GetFeePerByteAsync() + { + var result = await TestInvokeAsync(scriptHash, "getFeePerByte").ConfigureAwait(false); + return (long)result.Stack.Single().GetInteger(); + } + + /// + /// Get Ploicy Blocked Accounts + /// + /// + public async Task IsBlockedAsync(UInt160 account) + { + var result = await TestInvokeAsync(scriptHash, "isBlocked", new object[] { account }).ConfigureAwait(false); + return result.Stack.Single().GetBoolean(); + } + } +} diff --git a/src/Plugins/RpcClient/Properties/AssemblyInfo.cs b/src/Plugins/RpcClient/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..d464f67da1 --- /dev/null +++ b/src/Plugins/RpcClient/Properties/AssemblyInfo.cs @@ -0,0 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// AssemblyInfo.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Neo.Network.RPC.Tests")] diff --git a/src/Plugins/RpcClient/RpcClient.cs b/src/Plugins/RpcClient/RpcClient.cs new file mode 100644 index 0000000000..27b0023ec0 --- /dev/null +++ b/src/Plugins/RpcClient/RpcClient.cs @@ -0,0 +1,712 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcClient.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using Neo.Json; +using Neo.Network.P2P.Payloads; +using Neo.Network.RPC.Models; +using Neo.SmartContract; +using Neo.SmartContract.Manifest; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Neo.Network.RPC +{ + /// + /// The RPC client to call NEO RPC methods + /// + public class RpcClient : IDisposable + { + private readonly HttpClient httpClient; + private readonly Uri baseAddress; + internal readonly ProtocolSettings protocolSettings; + + public RpcClient(Uri url, string rpcUser = default, string rpcPass = default, ProtocolSettings protocolSettings = null) + { + httpClient = new HttpClient(); + baseAddress = url; + if (!string.IsNullOrEmpty(rpcUser) && !string.IsNullOrEmpty(rpcPass)) + { + string token = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{rpcUser}:{rpcPass}")); + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", token); + } + this.protocolSettings = protocolSettings ?? ProtocolSettings.Default; + } + + public RpcClient(HttpClient client, Uri url, ProtocolSettings protocolSettings = null) + { + httpClient = client; + baseAddress = url; + this.protocolSettings = protocolSettings ?? ProtocolSettings.Default; + } + + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + httpClient.Dispose(); + } + + disposedValue = true; + } + } + + public void Dispose() + { + Dispose(true); + } + #endregion + + static RpcRequest AsRpcRequest(string method, params JToken[] paraArgs) + { + return new RpcRequest + { + Id = 1, + JsonRpc = "2.0", + Method = method, + Params = paraArgs + }; + } + + static RpcResponse AsRpcResponse(string content, bool throwOnError) + { + var response = RpcResponse.FromJson((JObject)JToken.Parse(content)); + response.RawResponse = content; + + if (response.Error != null && throwOnError) + { + throw new RpcException(response.Error.Code, response.Error.Message); + } + + return response; + } + + HttpRequestMessage AsHttpRequest(RpcRequest request) + { + var requestJson = request.ToJson().ToString(); + return new HttpRequestMessage(HttpMethod.Post, baseAddress) + { + Content = new StringContent(requestJson, Neo.Utility.StrictUTF8) + }; + } + + public RpcResponse Send(RpcRequest request, bool throwOnError = true) + { + if (disposedValue) throw new ObjectDisposedException(nameof(RpcClient)); + + using var requestMsg = AsHttpRequest(request); + using var responseMsg = httpClient.Send(requestMsg); + using var contentStream = responseMsg.Content.ReadAsStream(); + using var contentReader = new StreamReader(contentStream); + return AsRpcResponse(contentReader.ReadToEnd(), throwOnError); + } + + public async Task SendAsync(RpcRequest request, bool throwOnError = true) + { + if (disposedValue) throw new ObjectDisposedException(nameof(RpcClient)); + + using var requestMsg = AsHttpRequest(request); + using var responseMsg = await httpClient.SendAsync(requestMsg).ConfigureAwait(false); + var content = await responseMsg.Content.ReadAsStringAsync(); + return AsRpcResponse(content, throwOnError); + } + + public virtual JToken RpcSend(string method, params JToken[] paraArgs) + { + var request = AsRpcRequest(method, paraArgs); + var response = Send(request); + return response.Result; + } + + public virtual async Task RpcSendAsync(string method, params JToken[] paraArgs) + { + var request = AsRpcRequest(method, paraArgs); + var response = await SendAsync(request).ConfigureAwait(false); + return response.Result; + } + + public static string GetRpcName([CallerMemberName] string methodName = null) + { + return new Regex("(.*?)(Hex|Both)?(Async)?").Replace(methodName, "$1").ToLowerInvariant(); + } + + #region Blockchain + + /// + /// Returns the hash of the tallest block in the main chain. + /// + public async Task GetBestBlockHashAsync() + { + var result = await RpcSendAsync(GetRpcName()).ConfigureAwait(false); + return result.AsString(); + } + + /// + /// Returns the hash of the tallest block in the main chain. + /// The serialized information of the block is returned, represented by a hexadecimal string. + /// + public async Task GetBlockHexAsync(string hashOrIndex) + { + var result = int.TryParse(hashOrIndex, out int index) + ? await RpcSendAsync(GetRpcName(), index).ConfigureAwait(false) + : await RpcSendAsync(GetRpcName(), hashOrIndex).ConfigureAwait(false); + return result.AsString(); + } + + /// + /// Returns the hash of the tallest block in the main chain. + /// + public async Task GetBlockAsync(string hashOrIndex) + { + var result = int.TryParse(hashOrIndex, out int index) + ? await RpcSendAsync(GetRpcName(), index, true).ConfigureAwait(false) + : await RpcSendAsync(GetRpcName(), hashOrIndex, true).ConfigureAwait(false); + + return RpcBlock.FromJson((JObject)result, protocolSettings); + } + + /// + /// Gets the number of block header in the main chain. + /// + public async Task GetBlockHeaderCountAsync() + { + var result = await RpcSendAsync(GetRpcName()).ConfigureAwait(false); + return (uint)result.AsNumber(); + } + + /// + /// Gets the number of blocks in the main chain. + /// + public async Task GetBlockCountAsync() + { + var result = await RpcSendAsync(GetRpcName()).ConfigureAwait(false); + return (uint)result.AsNumber(); + } + + /// + /// Returns the hash value of the corresponding block, based on the specified index. + /// + public async Task GetBlockHashAsync(uint index) + { + var result = await RpcSendAsync(GetRpcName(), index).ConfigureAwait(false); + return result.AsString(); + } + + /// + /// Returns the corresponding block header information according to the specified script hash. + /// + public async Task GetBlockHeaderHexAsync(string hashOrIndex) + { + var result = int.TryParse(hashOrIndex, out int index) + ? await RpcSendAsync(GetRpcName(), index).ConfigureAwait(false) + : await RpcSendAsync(GetRpcName(), hashOrIndex).ConfigureAwait(false); + return result.AsString(); + } + + /// + /// Returns the corresponding block header information according to the specified script hash. + /// + public async Task GetBlockHeaderAsync(string hashOrIndex) + { + var result = int.TryParse(hashOrIndex, out int index) + ? await RpcSendAsync(GetRpcName(), index, true).ConfigureAwait(false) + : await RpcSendAsync(GetRpcName(), hashOrIndex, true).ConfigureAwait(false); + + return RpcBlockHeader.FromJson((JObject)result, protocolSettings); + } + + /// + /// Queries contract information, according to the contract script hash. + /// + public async Task GetContractStateAsync(string hash) + { + var result = await RpcSendAsync(GetRpcName(), hash).ConfigureAwait(false); + return ContractStateFromJson((JObject)result); + } + + /// + /// Queries contract information, according to the contract id. + /// + public async Task GetContractStateAsync(int id) + { + var result = await RpcSendAsync(GetRpcName(), id).ConfigureAwait(false); + return ContractStateFromJson((JObject)result); + } + + public static ContractState ContractStateFromJson(JObject json) + { + return new ContractState + { + Id = (int)json["id"].AsNumber(), + UpdateCounter = (ushort)(json["updatecounter"]?.AsNumber() ?? 0), + Hash = UInt160.Parse(json["hash"].AsString()), + Nef = RpcNefFile.FromJson((JObject)json["nef"]), + Manifest = ContractManifest.FromJson((JObject)json["manifest"]) + }; + } + + /// + /// Get all native contracts. + /// + public async Task GetNativeContractsAsync() + { + var result = await RpcSendAsync(GetRpcName()).ConfigureAwait(false); + return ((JArray)result).Select(p => ContractStateFromJson((JObject)p)).ToArray(); + } + + /// + /// Obtains the list of unconfirmed transactions in memory. + /// + public async Task GetRawMempoolAsync() + { + var result = await RpcSendAsync(GetRpcName()).ConfigureAwait(false); + return ((JArray)result).Select(p => p.AsString()).ToArray(); + } + + /// + /// Obtains the list of unconfirmed transactions in memory. + /// shouldGetUnverified = true + /// + public async Task GetRawMempoolBothAsync() + { + var result = await RpcSendAsync(GetRpcName(), true).ConfigureAwait(false); + return RpcRawMemPool.FromJson((JObject)result); + } + + /// + /// Returns the corresponding transaction information, based on the specified hash value. + /// + public async Task GetRawTransactionHexAsync(string txHash) + { + var result = await RpcSendAsync(GetRpcName(), txHash).ConfigureAwait(false); + return result.AsString(); + } + + /// + /// Returns the corresponding transaction information, based on the specified hash value. + /// verbose = true + /// + public async Task GetRawTransactionAsync(string txHash) + { + var result = await RpcSendAsync(GetRpcName(), txHash, true).ConfigureAwait(false); + return RpcTransaction.FromJson((JObject)result, protocolSettings); + } + + /// + /// Calculate network fee + /// + /// Transaction + /// NetworkFee + public async Task CalculateNetworkFeeAsync(Transaction tx) + { + var json = await RpcSendAsync(GetRpcName(), Convert.ToBase64String(tx.ToArray())) + .ConfigureAwait(false); + return (long)json["networkfee"].AsNumber(); + } + + /// + /// Returns the stored value, according to the contract script hash (or Id) and the stored key. + /// + public async Task GetStorageAsync(string scriptHashOrId, string key) + { + var result = int.TryParse(scriptHashOrId, out int id) + ? await RpcSendAsync(GetRpcName(), id, key).ConfigureAwait(false) + : await RpcSendAsync(GetRpcName(), scriptHashOrId, key).ConfigureAwait(false); + return result.AsString(); + } + + /// + /// Returns the block index in which the transaction is found. + /// + public async Task GetTransactionHeightAsync(string txHash) + { + var result = await RpcSendAsync(GetRpcName(), txHash).ConfigureAwait(false); + return uint.Parse(result.AsString()); + } + + /// + /// Returns the next NEO consensus nodes information and voting status. + /// + public async Task GetNextBlockValidatorsAsync() + { + var result = await RpcSendAsync(GetRpcName()).ConfigureAwait(false); + return ((JArray)result).Select(p => RpcValidator.FromJson((JObject)p)).ToArray(); + } + + /// + /// Returns the current NEO committee members. + /// + public async Task GetCommitteeAsync() + { + var result = await RpcSendAsync(GetRpcName()).ConfigureAwait(false); + return ((JArray)result).Select(p => p.AsString()).ToArray(); + } + + #endregion Blockchain + + #region Node + + /// + /// Gets the current number of connections for the node. + /// + public async Task GetConnectionCountAsync() + { + var result = await RpcSendAsync(GetRpcName()).ConfigureAwait(false); + return (int)result.AsNumber(); + } + + /// + /// Gets the list of nodes that the node is currently connected/disconnected from. + /// + public async Task GetPeersAsync() + { + var result = await RpcSendAsync(GetRpcName()).ConfigureAwait(false); + return RpcPeers.FromJson((JObject)result); + } + + /// + /// Returns the version information about the queried node. + /// + public async Task GetVersionAsync() + { + var result = await RpcSendAsync(GetRpcName()).ConfigureAwait(false); + return RpcVersion.FromJson((JObject)result); + } + + /// + /// Broadcasts a serialized transaction over the NEO network. + /// + public async Task SendRawTransactionAsync(byte[] rawTransaction) + { + var result = await RpcSendAsync(GetRpcName(), Convert.ToBase64String(rawTransaction)).ConfigureAwait(false); + return UInt256.Parse(result["hash"].AsString()); + } + + /// + /// Broadcasts a transaction over the NEO network. + /// + public Task SendRawTransactionAsync(Transaction transaction) + { + return SendRawTransactionAsync(transaction.ToArray()); + } + + /// + /// Broadcasts a serialized block over the NEO network. + /// + public async Task SubmitBlockAsync(byte[] block) + { + var result = await RpcSendAsync(GetRpcName(), Convert.ToBase64String(block)).ConfigureAwait(false); + return UInt256.Parse(result["hash"].AsString()); + } + + #endregion Node + + #region SmartContract + + /// + /// Returns the result after calling a smart contract at scripthash with the given operation and parameters. + /// This RPC call does not affect the blockchain in any way. + /// + public async Task InvokeFunctionAsync(string scriptHash, string operation, RpcStack[] stacks, params Signer[] signer) + { + List parameters = new() { scriptHash.AsScriptHash(), operation, stacks.Select(p => p.ToJson()).ToArray() }; + if (signer.Length > 0) + { + parameters.Add(signer.Select(p => p.ToJson()).ToArray()); + } + var result = await RpcSendAsync(GetRpcName(), parameters.ToArray()).ConfigureAwait(false); + return RpcInvokeResult.FromJson((JObject)result); + } + + /// + /// Returns the result after passing a script through the VM. + /// This RPC call does not affect the blockchain in any way. + /// + public async Task InvokeScriptAsync(ReadOnlyMemory script, params Signer[] signers) + { + List parameters = new() { Convert.ToBase64String(script.Span) }; + if (signers.Length > 0) + { + parameters.Add(signers.Select(p => p.ToJson()).ToArray()); + } + var result = await RpcSendAsync(GetRpcName(), parameters.ToArray()).ConfigureAwait(false); + return RpcInvokeResult.FromJson((JObject)result); + } + + public async Task GetUnclaimedGasAsync(string address) + { + var result = await RpcSendAsync(GetRpcName(), address.AsScriptHash()).ConfigureAwait(false); + return RpcUnclaimedGas.FromJson((JObject)result); + } + + + public async IAsyncEnumerable TraverseIteratorAsync(string sessionId, string id) + { + const int count = 100; + while (true) + { + var result = await RpcSendAsync(GetRpcName(), sessionId, id, count).ConfigureAwait(false); + var array = (JArray)result; + foreach (JObject jObject in array) + { + yield return jObject; + } + if (array.Count < count) break; + } + } + + /// + /// Returns limit results from Iterator. + /// This RPC call does not affect the blockchain in any way. + /// + /// + /// + /// + /// + public async IAsyncEnumerable TraverseIteratorAsync(string sessionId, string id, int count) + { + var result = await RpcSendAsync(GetRpcName(), sessionId, id, count).ConfigureAwait(false); + if (result is JArray { Count: > 0 } array) + { + foreach (JObject jObject in array) + { + yield return jObject; + } + } + } + + /// + /// Terminate specified Iterator session. + /// This RPC call does not affect the blockchain in any way. + /// + public async Task TerminateSessionAsync(string sessionId) + { + var result = await RpcSendAsync(GetRpcName(), sessionId).ConfigureAwait(false); + return result.GetBoolean(); + } + + #endregion SmartContract + + #region Utilities + + /// + /// Returns a list of plugins loaded by the node. + /// + public async Task ListPluginsAsync() + { + var result = await RpcSendAsync(GetRpcName()).ConfigureAwait(false); + return ((JArray)result).Select(p => RpcPlugin.FromJson((JObject)p)).ToArray(); + } + + /// + /// Verifies that the address is a correct NEO address. + /// + public async Task ValidateAddressAsync(string address) + { + var result = await RpcSendAsync(GetRpcName(), address).ConfigureAwait(false); + return RpcValidateAddressResult.FromJson((JObject)result); + } + + #endregion Utilities + + #region Wallet + + /// + /// Close the wallet opened by RPC. + /// + public async Task CloseWalletAsync() + { + var result = await RpcSendAsync(GetRpcName()).ConfigureAwait(false); + return result.AsBoolean(); + } + + /// + /// Exports the private key of the specified address. + /// + public async Task DumpPrivKeyAsync(string address) + { + var result = await RpcSendAsync(GetRpcName(), address).ConfigureAwait(false); + return result.AsString(); + } + + /// + /// Creates a new account in the wallet opened by RPC. + /// + public async Task GetNewAddressAsync() + { + var result = await RpcSendAsync(GetRpcName()).ConfigureAwait(false); + return result.AsString(); + } + + /// + /// Returns the balance of the corresponding asset in the wallet, based on the specified asset Id. + /// This method applies to assets that conform to NEP-17 standards. + /// + /// new address as string + public async Task GetWalletBalanceAsync(string assetId) + { + var result = await RpcSendAsync(GetRpcName(), assetId).ConfigureAwait(false); + BigInteger balance = BigInteger.Parse(result["balance"].AsString()); + byte decimals = await new Nep17API(this).DecimalsAsync(UInt160.Parse(assetId.AsScriptHash())).ConfigureAwait(false); + return new BigDecimal(balance, decimals); + } + + /// + /// Gets the amount of unclaimed GAS in the wallet. + /// + public async Task GetWalletUnclaimedGasAsync() + { + var result = await RpcSendAsync(GetRpcName()).ConfigureAwait(false); + return BigDecimal.Parse(result.AsString(), SmartContract.Native.NativeContract.GAS.Decimals); + } + + /// + /// Imports the private key to the wallet. + /// + public async Task ImportPrivKeyAsync(string wif) + { + var result = await RpcSendAsync(GetRpcName(), wif).ConfigureAwait(false); + return RpcAccount.FromJson((JObject)result); + } + + /// + /// Lists all the accounts in the current wallet. + /// + public async Task> ListAddressAsync() + { + var result = await RpcSendAsync(GetRpcName()).ConfigureAwait(false); + return ((JArray)result).Select(p => RpcAccount.FromJson((JObject)p)).ToList(); + } + + /// + /// Open wallet file in the provider's machine. + /// By default, this method is disabled by RpcServer config.json. + /// + public async Task OpenWalletAsync(string path, string password) + { + var result = await RpcSendAsync(GetRpcName(), path, password).ConfigureAwait(false); + return result.AsBoolean(); + } + + /// + /// Transfer from the specified address to the destination address. + /// + /// This function returns Signed Transaction JSON if successful, ContractParametersContext JSON if signing failed. + public async Task SendFromAsync(string assetId, string fromAddress, string toAddress, string amount) + { + return (JObject)await RpcSendAsync(GetRpcName(), assetId.AsScriptHash(), fromAddress.AsScriptHash(), + toAddress.AsScriptHash(), amount).ConfigureAwait(false); + } + + /// + /// Bulk transfer order, and you can specify a sender address. + /// + /// This function returns Signed Transaction JSON if successful, ContractParametersContext JSON if signing failed. + public async Task SendManyAsync(string fromAddress, IEnumerable outputs) + { + var parameters = new List(); + if (!string.IsNullOrEmpty(fromAddress)) + { + parameters.Add(fromAddress.AsScriptHash()); + } + parameters.Add(outputs.Select(p => p.ToJson(protocolSettings)).ToArray()); + + return (JObject)await RpcSendAsync(GetRpcName(), paraArgs: parameters.ToArray()).ConfigureAwait(false); + } + + /// + /// Transfer asset from the wallet to the destination address. + /// + /// This function returns Signed Transaction JSON if successful, ContractParametersContext JSON if signing failed. + public async Task SendToAddressAsync(string assetId, string address, string amount) + { + return (JObject)await RpcSendAsync(GetRpcName(), assetId.AsScriptHash(), address.AsScriptHash(), amount) + .ConfigureAwait(false); + } + + /// + /// Cancel Tx. + /// + /// This function returns Signed Transaction JSON if successful, ContractParametersContext JSON if signing failed. + public async Task CancelTransactionAsync(UInt256 txId, string[] signers, string extraFee) + { + JToken[] parameters = signers.Select(s => (JString)s.AsScriptHash()).ToArray(); + return (JObject)await RpcSendAsync(GetRpcName(), txId.ToString(), new JArray(parameters), extraFee).ConfigureAwait(false); + } + + #endregion Wallet + + #region Plugins + + /// + /// Returns the contract log based on the specified txHash. The complete contract logs are stored under the ApplicationLogs directory. + /// This method is provided by the plugin ApplicationLogs. + /// + public async Task GetApplicationLogAsync(string txHash) + { + var result = await RpcSendAsync(GetRpcName(), txHash).ConfigureAwait(false); + return RpcApplicationLog.FromJson((JObject)result, protocolSettings); + } + + /// + /// Returns the contract log based on the specified txHash. The complete contract logs are stored under the ApplicationLogs directory. + /// This method is provided by the plugin ApplicationLogs. + /// + public async Task GetApplicationLogAsync(string txHash, TriggerType triggerType) + { + var result = await RpcSendAsync(GetRpcName(), txHash, triggerType).ConfigureAwait(false); + return RpcApplicationLog.FromJson((JObject)result, protocolSettings); + } + + /// + /// Returns all the NEP-17 transaction information occurred in the specified address. + /// This method is provided by the plugin RpcNep17Tracker. + /// + /// The address to query the transaction information. + /// The start block Timestamp, default to seven days before UtcNow + /// The end block Timestamp, default to UtcNow + public async Task GetNep17TransfersAsync(string address, ulong? startTimestamp = default, ulong? endTimestamp = default) + { + startTimestamp ??= 0; + endTimestamp ??= DateTime.UtcNow.ToTimestampMS(); + var result = await RpcSendAsync(GetRpcName(), address.AsScriptHash(), startTimestamp, endTimestamp) + .ConfigureAwait(false); + return RpcNep17Transfers.FromJson((JObject)result, protocolSettings); + } + + /// + /// Returns the balance of all NEP-17 assets in the specified address. + /// This method is provided by the plugin RpcNep17Tracker. + /// + public async Task GetNep17BalancesAsync(string address) + { + var result = await RpcSendAsync(GetRpcName(), address.AsScriptHash()) + .ConfigureAwait(false); + return RpcNep17Balances.FromJson((JObject)result, protocolSettings); + } + + #endregion Plugins + } +} diff --git a/src/Plugins/RpcClient/RpcClient.csproj b/src/Plugins/RpcClient/RpcClient.csproj new file mode 100644 index 0000000000..bc6161e3cb --- /dev/null +++ b/src/Plugins/RpcClient/RpcClient.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + Neo.Network.RPC.RpcClient + Neo.Network.RPC + ../../../bin/$(PackageId) + + + + + + + diff --git a/src/Plugins/RpcClient/RpcException.cs b/src/Plugins/RpcClient/RpcException.cs new file mode 100644 index 0000000000..d0f2e5e64b --- /dev/null +++ b/src/Plugins/RpcClient/RpcException.cs @@ -0,0 +1,23 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcException.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; + +namespace Neo.Network.RPC +{ + public class RpcException : Exception + { + public RpcException(int code, string message) : base(message) + { + HResult = code; + } + } +} diff --git a/src/Plugins/RpcClient/StateAPI.cs b/src/Plugins/RpcClient/StateAPI.cs new file mode 100644 index 0000000000..de8baa8077 --- /dev/null +++ b/src/Plugins/RpcClient/StateAPI.cs @@ -0,0 +1,88 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// StateAPI.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using Neo.Network.RPC.Models; +using System; +using System.Threading.Tasks; + +namespace Neo.Network.RPC +{ + public class StateAPI + { + private readonly RpcClient rpcClient; + + public StateAPI(RpcClient rpc) + { + rpcClient = rpc; + } + + public async Task GetStateRootAsync(uint index) + { + var result = await rpcClient.RpcSendAsync(RpcClient.GetRpcName(), index).ConfigureAwait(false); + return RpcStateRoot.FromJson((JObject)result); + } + + public async Task GetProofAsync(UInt256 rootHash, UInt160 scriptHash, byte[] key) + { + var result = await rpcClient.RpcSendAsync(RpcClient.GetRpcName(), + rootHash.ToString(), scriptHash.ToString(), Convert.ToBase64String(key)).ConfigureAwait(false); + return Convert.FromBase64String(result.AsString()); + } + + public async Task VerifyProofAsync(UInt256 rootHash, byte[] proofBytes) + { + var result = await rpcClient.RpcSendAsync(RpcClient.GetRpcName(), + rootHash.ToString(), Convert.ToBase64String(proofBytes)).ConfigureAwait(false); + + return Convert.FromBase64String(result.AsString()); + } + + public async Task<(uint? localRootIndex, uint? validatedRootIndex)> GetStateHeightAsync() + { + var result = await rpcClient.RpcSendAsync(RpcClient.GetRpcName()).ConfigureAwait(false); + var localRootIndex = ToNullableUint(result["localrootindex"]); + var validatedRootIndex = ToNullableUint(result["validatedrootindex"]); + return (localRootIndex, validatedRootIndex); + } + + static uint? ToNullableUint(JToken json) => (json == null) ? null : (uint?)json.AsNumber(); + + public static JToken[] MakeFindStatesParams(UInt256 rootHash, UInt160 scriptHash, ReadOnlySpan prefix, ReadOnlySpan from = default, int? count = null) + { + var @params = new JToken[count.HasValue ? 5 : 4]; + @params[0] = rootHash.ToString(); + @params[1] = scriptHash.ToString(); + @params[2] = Convert.ToBase64String(prefix); + @params[3] = Convert.ToBase64String(from); + if (count.HasValue) + { + @params[4] = count.Value; + } + return @params; + } + + public async Task FindStatesAsync(UInt256 rootHash, UInt160 scriptHash, ReadOnlyMemory prefix, ReadOnlyMemory from = default, int? count = null) + { + var @params = MakeFindStatesParams(rootHash, scriptHash, prefix.Span, from.Span, count); + var result = await rpcClient.RpcSendAsync(RpcClient.GetRpcName(), @params).ConfigureAwait(false); + + return RpcFoundStates.FromJson((JObject)result); + } + + public async Task GetStateAsync(UInt256 rootHash, UInt160 scriptHash, byte[] key) + { + var result = await rpcClient.RpcSendAsync(RpcClient.GetRpcName(), + rootHash.ToString(), scriptHash.ToString(), Convert.ToBase64String(key)).ConfigureAwait(false); + return Convert.FromBase64String(result.AsString()); + } + } +} diff --git a/src/Plugins/RpcClient/TransactionManager.cs b/src/Plugins/RpcClient/TransactionManager.cs new file mode 100644 index 0000000000..ac20eed450 --- /dev/null +++ b/src/Plugins/RpcClient/TransactionManager.cs @@ -0,0 +1,215 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TransactionManager.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Cryptography.ECC; +using Neo.IO; +using Neo.Network.P2P.Payloads; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.Wallets; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Neo.Network.RPC +{ + /// + /// This class helps to create transaction with RPC API. + /// + public class TransactionManager + { + private class SignItem { public Contract Contract; public HashSet KeyPairs; } + + private readonly RpcClient rpcClient; + + /// + /// The Transaction context to manage the witnesses + /// + private readonly ContractParametersContext context; + + /// + /// This container stores the keys for sign the transaction + /// + private readonly List signStore = new List(); + + /// + /// The Transaction managed by this instance + /// + private readonly Transaction tx; + + public Transaction Tx => tx; + + /// + /// TransactionManager Constructor + /// + /// the transaction to manage. Typically buildt + /// the RPC client to call NEO RPC API + public TransactionManager(Transaction tx, RpcClient rpcClient) + { + this.tx = tx; + context = new ContractParametersContext(null, tx, rpcClient.protocolSettings.Network); + this.rpcClient = rpcClient; + } + + /// + /// Helper function for one-off TransactionManager creation + /// + public static Task MakeTransactionAsync(RpcClient rpcClient, ReadOnlyMemory script, Signer[] signers = null, TransactionAttribute[] attributes = null) + { + var factory = new TransactionManagerFactory(rpcClient); + return factory.MakeTransactionAsync(script, signers, attributes); + } + + /// + /// Helper function for one-off TransactionManager creation + /// + public static Task MakeTransactionAsync(RpcClient rpcClient, ReadOnlyMemory script, long systemFee, Signer[] signers = null, TransactionAttribute[] attributes = null) + { + var factory = new TransactionManagerFactory(rpcClient); + return factory.MakeTransactionAsync(script, systemFee, signers, attributes); + } + + /// + /// Add Signature + /// + /// The KeyPair to sign transction + /// + public TransactionManager AddSignature(KeyPair key) + { + var contract = Contract.CreateSignatureContract(key.PublicKey); + AddSignItem(contract, key); + return this; + } + + /// + /// Add Multi-Signature + /// + /// The KeyPair to sign transction + /// The least count of signatures needed for multiple signature contract + /// The Public Keys construct the multiple signature contract + public TransactionManager AddMultiSig(KeyPair key, int m, params ECPoint[] publicKeys) + { + Contract contract = Contract.CreateMultiSigContract(m, publicKeys); + AddSignItem(contract, key); + return this; + } + + /// + /// Add Multi-Signature + /// + /// The KeyPairs to sign transction + /// The least count of signatures needed for multiple signature contract + /// The Public Keys construct the multiple signature contract + public TransactionManager AddMultiSig(KeyPair[] keys, int m, params ECPoint[] publicKeys) + { + Contract contract = Contract.CreateMultiSigContract(m, publicKeys); + for (int i = 0; i < keys.Length; i++) + { + AddSignItem(contract, keys[i]); + } + return this; + } + + private void AddSignItem(Contract contract, KeyPair key) + { + if (!Tx.GetScriptHashesForVerifying(null).Contains(contract.ScriptHash)) + { + throw new Exception($"Add SignItem error: Mismatch ScriptHash ({contract.ScriptHash})"); + } + + SignItem item = signStore.FirstOrDefault(p => p.Contract.ScriptHash == contract.ScriptHash); + if (item is null) + { + signStore.Add(new SignItem { Contract = contract, KeyPairs = new HashSet { key } }); + } + else if (!item.KeyPairs.Contains(key)) + { + item.KeyPairs.Add(key); + } + } + + /// + /// Add Witness with contract + /// + /// The witness verification contract + /// The witness invocation parameters + public TransactionManager AddWitness(Contract contract, params object[] parameters) + { + if (!context.Add(contract, parameters)) + { + throw new Exception("AddWitness failed!"); + }; + return this; + } + + /// + /// Add Witness with scriptHash + /// + /// The witness verification contract hash + /// The witness invocation parameters + public TransactionManager AddWitness(UInt160 scriptHash, params object[] parameters) + { + var contract = Contract.Create(scriptHash); + return AddWitness(contract, parameters); + } + + /// + /// Verify Witness count and add witnesses + /// + public async Task SignAsync() + { + // Calculate NetworkFee + Tx.Witnesses = Tx.GetScriptHashesForVerifying(null).Select(u => new Witness() + { + InvocationScript = Array.Empty(), + VerificationScript = GetVerificationScript(u) + }).ToArray(); + Tx.NetworkFee = await rpcClient.CalculateNetworkFeeAsync(Tx).ConfigureAwait(false); + Tx.Witnesses = null; + + var gasBalance = await new Nep17API(rpcClient).BalanceOfAsync(NativeContract.GAS.Hash, Tx.Sender).ConfigureAwait(false); + if (gasBalance < Tx.SystemFee + Tx.NetworkFee) + throw new InvalidOperationException($"Insufficient GAS in address: {Tx.Sender.ToAddress(rpcClient.protocolSettings.AddressVersion)}"); + + // Sign with signStore + for (int i = 0; i < signStore.Count; i++) + { + foreach (var key in signStore[i].KeyPairs) + { + byte[] signature = Tx.Sign(key, rpcClient.protocolSettings.Network); + if (!context.AddSignature(signStore[i].Contract, key.PublicKey, signature)) + { + throw new Exception("AddSignature failed!"); + } + } + } + + // Verify witness count + if (!context.Completed) + { + throw new Exception($"Please add signature or witness first!"); + } + Tx.Witnesses = context.GetWitnesses(); + return Tx; + } + + private byte[] GetVerificationScript(UInt160 hash) + { + foreach (var item in signStore) + { + if (item.Contract.ScriptHash == hash) return item.Contract.Script; + } + + return Array.Empty(); + } + } +} diff --git a/src/Plugins/RpcClient/TransactionManagerFactory.cs b/src/Plugins/RpcClient/TransactionManagerFactory.cs new file mode 100644 index 0000000000..3fe9e3ded5 --- /dev/null +++ b/src/Plugins/RpcClient/TransactionManagerFactory.cs @@ -0,0 +1,71 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TransactionManagerFactory.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Network.P2P.Payloads; +using Neo.Network.RPC.Models; +using System; +using System.Threading.Tasks; + +namespace Neo.Network.RPC +{ + public class TransactionManagerFactory + { + private readonly RpcClient rpcClient; + + /// + /// TransactionManagerFactory Constructor + /// + /// the RPC client to call NEO RPC API + public TransactionManagerFactory(RpcClient rpcClient) + { + this.rpcClient = rpcClient; + } + + /// + /// Create an unsigned Transaction object with given parameters. + /// + /// Transaction Script + /// Transaction Signers + /// Transaction Attributes + /// + public async Task MakeTransactionAsync(ReadOnlyMemory script, Signer[] signers = null, TransactionAttribute[] attributes = null) + { + RpcInvokeResult invokeResult = await rpcClient.InvokeScriptAsync(script, signers).ConfigureAwait(false); + return await MakeTransactionAsync(script, invokeResult.GasConsumed, signers, attributes).ConfigureAwait(false); + } + + /// + /// Create an unsigned Transaction object with given parameters. + /// + /// Transaction Script + /// Transaction System Fee + /// Transaction Signers + /// Transaction Attributes + /// + public async Task MakeTransactionAsync(ReadOnlyMemory script, long systemFee, Signer[] signers = null, TransactionAttribute[] attributes = null) + { + uint blockCount = await rpcClient.GetBlockCountAsync().ConfigureAwait(false) - 1; + + var tx = new Transaction + { + Version = 0, + Nonce = (uint)new Random().Next(), + Script = script, + Signers = signers ?? Array.Empty(), + ValidUntilBlock = blockCount - 1 + rpcClient.protocolSettings.MaxValidUntilBlockIncrement, + SystemFee = systemFee, + Attributes = attributes ?? Array.Empty(), + }; + + return new TransactionManager(tx, rpcClient); + } + } +} diff --git a/src/Plugins/RpcClient/Utility.cs b/src/Plugins/RpcClient/Utility.cs new file mode 100644 index 0000000000..659942f8f8 --- /dev/null +++ b/src/Plugins/RpcClient/Utility.cs @@ -0,0 +1,298 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Utility.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Cryptography.ECC; +using Neo.Json; +using Neo.Network.P2P.Payloads; +using Neo.Network.P2P.Payloads.Conditions; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM.Types; +using Neo.Wallets; +using System; +using System.Linq; +using System.Numerics; +using Array = Neo.VM.Types.Array; +using Buffer = Neo.VM.Types.Buffer; + +namespace Neo.Network.RPC +{ + public static class Utility + { + private static (BigInteger numerator, BigInteger denominator) Fraction(decimal d) + { + int[] bits = decimal.GetBits(d); + BigInteger numerator = (1 - ((bits[3] >> 30) & 2)) * + unchecked(((BigInteger)(uint)bits[2] << 64) | + ((BigInteger)(uint)bits[1] << 32) | + (uint)bits[0]); + BigInteger denominator = BigInteger.Pow(10, (bits[3] >> 16) & 0xff); + return (numerator, denominator); + } + + public static UInt160 ToScriptHash(this JToken value, ProtocolSettings protocolSettings) + { + var addressOrScriptHash = value.AsString(); + + return addressOrScriptHash.Length < 40 ? + addressOrScriptHash.ToScriptHash(protocolSettings.AddressVersion) : UInt160.Parse(addressOrScriptHash); + } + + public static string AsScriptHash(this string addressOrScriptHash) + { + foreach (var native in NativeContract.Contracts) + { + if (addressOrScriptHash.Equals(native.Name, StringComparison.InvariantCultureIgnoreCase) || + addressOrScriptHash == native.Id.ToString()) + return native.Hash.ToString(); + } + + return addressOrScriptHash.Length < 40 ? + addressOrScriptHash : UInt160.Parse(addressOrScriptHash).ToString(); + } + + /// + /// Parse WIF or private key hex string to KeyPair + /// + /// WIF or private key hex string + /// Example: WIF ("KyXwTh1hB76RRMquSvnxZrJzQx7h9nQP2PCRL38v6VDb5ip3nf1p"), PrivateKey ("450d6c2a04b5b470339a745427bae6828400cf048400837d73c415063835e005") + /// + public static KeyPair GetKeyPair(string key) + { + if (string.IsNullOrEmpty(key)) { throw new ArgumentNullException(nameof(key)); } + if (key.StartsWith("0x")) { key = key[2..]; } + + return key.Length switch + { + 52 => new KeyPair(Wallet.GetPrivateKeyFromWIF(key)), + 64 => new KeyPair(key.HexToBytes()), + _ => throw new FormatException() + }; + } + + /// + /// Parse address, scripthash or public key string to UInt160 + /// + /// account address, scripthash or public key string + /// Example: address ("Ncm9TEzrp8SSer6Wa3UCSLTRnqzwVhCfuE"), scripthash ("0xb0a31817c80ad5f87b6ed390ecb3f9d312f7ceb8"), public key ("02f9ec1fd0a98796cf75b586772a4ddd41a0af07a1dbdf86a7238f74fb72503575") + /// The protocol settings + /// + public static UInt160 GetScriptHash(string account, ProtocolSettings protocolSettings) + { + if (string.IsNullOrEmpty(account)) { throw new ArgumentNullException(nameof(account)); } + if (account.StartsWith("0x")) { account = account[2..]; } + + return account.Length switch + { + 34 => account.ToScriptHash(protocolSettings.AddressVersion), + 40 => UInt160.Parse(account), + 66 => Contract.CreateSignatureRedeemScript(ECPoint.Parse(account, ECCurve.Secp256r1)).ToScriptHash(), + _ => throw new FormatException(), + }; + } + + /// + /// Convert decimal amount to BigInteger: amount * 10 ^ decimals + /// + /// float value + /// token decimals + /// + public static BigInteger ToBigInteger(this decimal amount, uint decimals) + { + BigInteger factor = BigInteger.Pow(10, (int)decimals); + var (numerator, denominator) = Fraction(amount); + if (factor < denominator) + { + throw new ArgumentException("The decimal places is too long."); + } + + BigInteger res = factor * numerator / denominator; + return res; + } + + public static Block BlockFromJson(JObject json, ProtocolSettings protocolSettings) + { + return new Block() + { + Header = HeaderFromJson(json, protocolSettings), + Transactions = ((JArray)json["tx"]).Select(p => TransactionFromJson((JObject)p, protocolSettings)).ToArray() + }; + } + + public static JObject BlockToJson(Block block, ProtocolSettings protocolSettings) + { + JObject json = block.ToJson(protocolSettings); + json["tx"] = block.Transactions.Select(p => TransactionToJson(p, protocolSettings)).ToArray(); + return json; + } + + public static Header HeaderFromJson(JObject json, ProtocolSettings protocolSettings) + { + return new Header + { + Version = (uint)json["version"].AsNumber(), + PrevHash = UInt256.Parse(json["previousblockhash"].AsString()), + MerkleRoot = UInt256.Parse(json["merkleroot"].AsString()), + Timestamp = (ulong)json["time"].AsNumber(), + Nonce = Convert.ToUInt64(json["nonce"].AsString(), 16), + Index = (uint)json["index"].AsNumber(), + PrimaryIndex = (byte)json["primary"].AsNumber(), + NextConsensus = json["nextconsensus"].ToScriptHash(protocolSettings), + Witness = ((JArray)json["witnesses"]).Select(p => WitnessFromJson((JObject)p)).FirstOrDefault() + }; + } + + public static Transaction TransactionFromJson(JObject json, ProtocolSettings protocolSettings) + { + return new Transaction + { + Version = byte.Parse(json["version"].AsString()), + Nonce = uint.Parse(json["nonce"].AsString()), + Signers = ((JArray)json["signers"]).Select(p => SignerFromJson((JObject)p, protocolSettings)).ToArray(), + SystemFee = long.Parse(json["sysfee"].AsString()), + NetworkFee = long.Parse(json["netfee"].AsString()), + ValidUntilBlock = uint.Parse(json["validuntilblock"].AsString()), + Attributes = ((JArray)json["attributes"]).Select(p => TransactionAttributeFromJson((JObject)p)).ToArray(), + Script = Convert.FromBase64String(json["script"].AsString()), + Witnesses = ((JArray)json["witnesses"]).Select(p => WitnessFromJson((JObject)p)).ToArray() + }; + } + + public static JObject TransactionToJson(Transaction tx, ProtocolSettings protocolSettings) + { + JObject json = tx.ToJson(protocolSettings); + json["sysfee"] = tx.SystemFee.ToString(); + json["netfee"] = tx.NetworkFee.ToString(); + return json; + } + + public static Signer SignerFromJson(JObject json, ProtocolSettings protocolSettings) + { + return new Signer + { + Account = json["account"].ToScriptHash(protocolSettings), + Rules = ((JArray)json["rules"])?.Select(p => RuleFromJson((JObject)p, protocolSettings)).ToArray(), + Scopes = (WitnessScope)Enum.Parse(typeof(WitnessScope), json["scopes"].AsString()), + AllowedContracts = ((JArray)json["allowedcontracts"])?.Select(p => p.ToScriptHash(protocolSettings)).ToArray(), + AllowedGroups = ((JArray)json["allowedgroups"])?.Select(p => ECPoint.Parse(p.AsString(), ECCurve.Secp256r1)).ToArray() + }; + } + + public static TransactionAttribute TransactionAttributeFromJson(JObject json) + { + TransactionAttributeType usage = Enum.Parse(json["type"].AsString()); + return usage switch + { + TransactionAttributeType.HighPriority => new HighPriorityAttribute(), + TransactionAttributeType.OracleResponse => new OracleResponse() + { + Id = (ulong)json["id"].AsNumber(), + Code = Enum.Parse(json["code"].AsString()), + Result = Convert.FromBase64String(json["result"].AsString()), + }, + TransactionAttributeType.NotValidBefore => new NotValidBefore() + { + Height = (uint)json["height"].AsNumber(), + }, + TransactionAttributeType.Conflicts => new Conflicts() + { + Hash = UInt256.Parse(json["hash"].AsString()) + }, + _ => throw new FormatException(), + }; + } + + public static Witness WitnessFromJson(JObject json) + { + return new Witness + { + InvocationScript = Convert.FromBase64String(json["invocation"].AsString()), + VerificationScript = Convert.FromBase64String(json["verification"].AsString()) + }; + } + + public static WitnessRule RuleFromJson(JObject json, ProtocolSettings protocolSettings) + { + return new WitnessRule() + { + Action = Enum.Parse(json["action"].AsString()), + Condition = RuleExpressionFromJson((JObject)json["condition"], protocolSettings) + }; + } + + public static WitnessCondition RuleExpressionFromJson(JObject json, ProtocolSettings protocolSettings) + { + return json["type"].AsString() switch + { + "Or" => new OrCondition { Expressions = ((JArray)json["expressions"])?.Select(p => RuleExpressionFromJson((JObject)p, protocolSettings)).ToArray() }, + "And" => new AndCondition { Expressions = ((JArray)json["expressions"])?.Select(p => RuleExpressionFromJson((JObject)p, protocolSettings)).ToArray() }, + "Boolean" => new BooleanCondition { Expression = json["expression"].AsBoolean() }, + "Not" => new NotCondition { Expression = RuleExpressionFromJson((JObject)json["expression"], protocolSettings) }, + "Group" => new GroupCondition { Group = ECPoint.Parse(json["group"].AsString(), ECCurve.Secp256r1) }, + "CalledByContract" => new CalledByContractCondition { Hash = json["hash"].ToScriptHash(protocolSettings) }, + "ScriptHash" => new ScriptHashCondition { Hash = json["hash"].ToScriptHash(protocolSettings) }, + "CalledByEntry" => new CalledByEntryCondition(), + "CalledByGroup" => new CalledByGroupCondition { Group = ECPoint.Parse(json["group"].AsString(), ECCurve.Secp256r1) }, + _ => throw new FormatException("Wrong rule's condition type"), + }; + } + + public static StackItem StackItemFromJson(JObject json) + { + StackItemType type = json["type"].GetEnum(); + switch (type) + { + case StackItemType.Boolean: + return json["value"].GetBoolean() ? StackItem.True : StackItem.False; + case StackItemType.Buffer: + return new Buffer(Convert.FromBase64String(json["value"].AsString())); + case StackItemType.ByteString: + return new ByteString(Convert.FromBase64String(json["value"].AsString())); + case StackItemType.Integer: + return BigInteger.Parse(json["value"].AsString()); + case StackItemType.Array: + Array array = new(); + foreach (JObject item in (JArray)json["value"]) + array.Add(StackItemFromJson(item)); + return array; + case StackItemType.Struct: + Struct @struct = new(); + foreach (JObject item in (JArray)json["value"]) + @struct.Add(StackItemFromJson(item)); + return @struct; + case StackItemType.Map: + Map map = new(); + foreach (var item in (JArray)json["value"]) + { + PrimitiveType key = (PrimitiveType)StackItemFromJson((JObject)item["key"]); + map[key] = StackItemFromJson((JObject)item["value"]); + } + return map; + case StackItemType.Pointer: + return new Pointer(null, (int)json["value"].AsNumber()); + case StackItemType.InteropInterface: + return new InteropInterface(json); + default: + return json["value"]?.AsString() ?? StackItem.Null; + } + } + + public static string GetIteratorId(this StackItem item) + { + if (item is InteropInterface iop) + { + var json = iop.GetInterface(); + return json["id"]?.GetString(); + } + return null; + } + } +} diff --git a/src/Plugins/RpcClient/WalletAPI.cs b/src/Plugins/RpcClient/WalletAPI.cs new file mode 100644 index 0000000000..bbf684f758 --- /dev/null +++ b/src/Plugins/RpcClient/WalletAPI.cs @@ -0,0 +1,225 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// WalletAPI.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Cryptography.ECC; +using Neo.Network.P2P.Payloads; +using Neo.Network.RPC.Models; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.Wallets; +using System; +using System.Linq; +using System.Numerics; +using System.Threading.Tasks; + +namespace Neo.Network.RPC +{ + /// + /// Wallet Common APIs + /// + public class WalletAPI + { + private readonly RpcClient rpcClient; + private readonly Nep17API nep17API; + + /// + /// WalletAPI Constructor + /// + /// the RPC client to call NEO RPC methods + public WalletAPI(RpcClient rpc) + { + rpcClient = rpc; + nep17API = new Nep17API(rpc); + } + + /// + /// Get unclaimed gas with address, scripthash or public key string + /// + /// address, scripthash or public key string + /// Example: address ("Ncm9TEzrp8SSer6Wa3UCSLTRnqzwVhCfuE"), scripthash ("0xb0a31817c80ad5f87b6ed390ecb3f9d312f7ceb8"), public key ("02f9ec1fd0a98796cf75b586772a4ddd41a0af07a1dbdf86a7238f74fb72503575") + /// + public Task GetUnclaimedGasAsync(string account) + { + UInt160 accountHash = Utility.GetScriptHash(account, rpcClient.protocolSettings); + return GetUnclaimedGasAsync(accountHash); + } + + /// + /// Get unclaimed gas + /// + /// account scripthash + /// + public async Task GetUnclaimedGasAsync(UInt160 account) + { + UInt160 scriptHash = NativeContract.NEO.Hash; + var blockCount = await rpcClient.GetBlockCountAsync().ConfigureAwait(false); + var result = await nep17API.TestInvokeAsync(scriptHash, "unclaimedGas", account, blockCount - 1).ConfigureAwait(false); + BigInteger balance = result.Stack.Single().GetInteger(); + return ((decimal)balance) / (long)NativeContract.GAS.Factor; + } + + /// + /// Get Neo Balance + /// + /// address, scripthash or public key string + /// Example: address ("Ncm9TEzrp8SSer6Wa3UCSLTRnqzwVhCfuE"), scripthash ("0xb0a31817c80ad5f87b6ed390ecb3f9d312f7ceb8"), public key ("02f9ec1fd0a98796cf75b586772a4ddd41a0af07a1dbdf86a7238f74fb72503575") + /// + public async Task GetNeoBalanceAsync(string account) + { + BigInteger balance = await GetTokenBalanceAsync(NativeContract.NEO.Hash.ToString(), account).ConfigureAwait(false); + return (uint)balance; + } + + /// + /// Get Gas Balance + /// + /// address, scripthash or public key string + /// Example: address ("Ncm9TEzrp8SSer6Wa3UCSLTRnqzwVhCfuE"), scripthash ("0xb0a31817c80ad5f87b6ed390ecb3f9d312f7ceb8"), public key ("02f9ec1fd0a98796cf75b586772a4ddd41a0af07a1dbdf86a7238f74fb72503575") + /// + public async Task GetGasBalanceAsync(string account) + { + BigInteger balance = await GetTokenBalanceAsync(NativeContract.GAS.Hash.ToString(), account).ConfigureAwait(false); + return ((decimal)balance) / (long)NativeContract.GAS.Factor; + } + + /// + /// Get token balance with string parameters + /// + /// token script hash, Example: "0x43cf98eddbe047e198a3e5d57006311442a0ca15"(NEO) + /// address, scripthash or public key string + /// Example: address ("Ncm9TEzrp8SSer6Wa3UCSLTRnqzwVhCfuE"), scripthash ("0xb0a31817c80ad5f87b6ed390ecb3f9d312f7ceb8"), public key ("02f9ec1fd0a98796cf75b586772a4ddd41a0af07a1dbdf86a7238f74fb72503575") + /// + public Task GetTokenBalanceAsync(string tokenHash, string account) + { + UInt160 scriptHash = Utility.GetScriptHash(tokenHash, rpcClient.protocolSettings); + UInt160 accountHash = Utility.GetScriptHash(account, rpcClient.protocolSettings); + return nep17API.BalanceOfAsync(scriptHash, accountHash); + } + + /// + /// The GAS is claimed when doing NEO transfer + /// This function will transfer NEO balance from account to itself + /// + /// wif or private key + /// Example: WIF ("KyXwTh1hB76RRMquSvnxZrJzQx7h9nQP2PCRL38v6VDb5ip3nf1p"), PrivateKey ("450d6c2a04b5b470339a745427bae6828400cf048400837d73c415063835e005") + /// Add assert at the end of the script + /// The transaction sended + public Task ClaimGasAsync(string key, bool addAssert = true) + { + KeyPair keyPair = Utility.GetKeyPair(key); + return ClaimGasAsync(keyPair, addAssert); + } + + /// + /// The GAS is claimed when doing NEO transfer + /// This function will transfer NEO balance from account to itself + /// + /// keyPair + /// Add assert at the end of the script + /// The transaction sended + public async Task ClaimGasAsync(KeyPair keyPair, bool addAssert = true) + { + UInt160 toHash = Contract.CreateSignatureRedeemScript(keyPair.PublicKey).ToScriptHash(); + BigInteger balance = await nep17API.BalanceOfAsync(NativeContract.NEO.Hash, toHash).ConfigureAwait(false); + Transaction transaction = await nep17API.CreateTransferTxAsync(NativeContract.NEO.Hash, keyPair, toHash, balance, null, addAssert).ConfigureAwait(false); + await rpcClient.SendRawTransactionAsync(transaction).ConfigureAwait(false); + return transaction; + } + + /// + /// Transfer NEP17 token balance, with common data types + /// + /// nep17 token script hash, Example: scripthash ("0xb0a31817c80ad5f87b6ed390ecb3f9d312f7ceb8") + /// wif or private key + /// Example: WIF ("KyXwTh1hB76RRMquSvnxZrJzQx7h9nQP2PCRL38v6VDb5ip3nf1p"), PrivateKey ("450d6c2a04b5b470339a745427bae6828400cf048400837d73c415063835e005") + /// address or account script hash + /// token amount + /// onPayment data + /// Add assert at the end of the script + /// + public async Task TransferAsync(string tokenHash, string fromKey, string toAddress, decimal amount, object data = null, bool addAssert = true) + { + UInt160 scriptHash = Utility.GetScriptHash(tokenHash, rpcClient.protocolSettings); + var decimals = await nep17API.DecimalsAsync(scriptHash).ConfigureAwait(false); + + KeyPair from = Utility.GetKeyPair(fromKey); + UInt160 to = Utility.GetScriptHash(toAddress, rpcClient.protocolSettings); + BigInteger amountInteger = amount.ToBigInteger(decimals); + return await TransferAsync(scriptHash, from, to, amountInteger, data, addAssert).ConfigureAwait(false); + } + + /// + /// Transfer NEP17 token from single-sig account + /// + /// contract script hash + /// from KeyPair + /// to account script hash + /// transfer amount + /// onPayment data + /// Add assert at the end of the script + /// + public async Task TransferAsync(UInt160 scriptHash, KeyPair from, UInt160 to, BigInteger amountInteger, object data = null, bool addAssert = true) + { + Transaction transaction = await nep17API.CreateTransferTxAsync(scriptHash, from, to, amountInteger, data, addAssert).ConfigureAwait(false); + await rpcClient.SendRawTransactionAsync(transaction).ConfigureAwait(false); + return transaction; + } + + /// + /// Transfer NEP17 token from multi-sig account + /// + /// contract script hash + /// multi-sig min signature count + /// multi-sig pubKeys + /// sign keys + /// to account + /// transfer amount + /// onPayment data + /// Add assert at the end of the script + /// + public async Task TransferAsync(UInt160 scriptHash, int m, ECPoint[] pubKeys, KeyPair[] keys, UInt160 to, BigInteger amountInteger, object data = null, bool addAssert = true) + { + Transaction transaction = await nep17API.CreateTransferTxAsync(scriptHash, m, pubKeys, keys, to, amountInteger, data, addAssert).ConfigureAwait(false); + await rpcClient.SendRawTransactionAsync(transaction).ConfigureAwait(false); + return transaction; + } + + /// + /// Wait until the transaction is observable block chain + /// + /// the transaction to observe + /// TimeoutException throws after "timeout" seconds + /// the Transaction state, including vmState and blockhash + public async Task WaitTransactionAsync(Transaction transaction, int timeout = 60) + { + DateTime deadline = DateTime.UtcNow.AddSeconds(timeout); + RpcTransaction rpcTx = null; + while (rpcTx == null || rpcTx.Confirmations == null) + { + if (deadline < DateTime.UtcNow) + { + throw new TimeoutException(); + } + + try + { + rpcTx = await rpcClient.GetRawTransactionAsync(transaction.Hash.ToString()).ConfigureAwait(false); + if (rpcTx == null || rpcTx.Confirmations == null) + { + await Task.Delay((int)rpcClient.protocolSettings.MillisecondsPerBlock / 2); + } + } + catch (Exception) { } + } + return rpcTx; + } + } +} diff --git a/src/Plugins/RpcServer/Diagnostic.cs b/src/Plugins/RpcServer/Diagnostic.cs new file mode 100644 index 0000000000..7363bc852c --- /dev/null +++ b/src/Plugins/RpcServer/Diagnostic.cs @@ -0,0 +1,53 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Diagnostic.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.SmartContract; +using Neo.VM; + +namespace Neo.Plugins.RpcServer +{ + class Diagnostic : IDiagnostic + { + public Tree InvocationTree { get; } = new(); + + private TreeNode currentNodeOfInvocationTree = null; + + public void Initialized(ApplicationEngine engine) + { + } + + public void Disposed() + { + } + + public void ContextLoaded(ExecutionContext context) + { + var state = context.GetState(); + if (currentNodeOfInvocationTree is null) + currentNodeOfInvocationTree = InvocationTree.AddRoot(state.ScriptHash); + else + currentNodeOfInvocationTree = currentNodeOfInvocationTree.AddChild(state.ScriptHash); + } + + public void ContextUnloaded(ExecutionContext context) + { + currentNodeOfInvocationTree = currentNodeOfInvocationTree.Parent; + } + + public void PreExecuteInstruction(Instruction instruction) + { + } + + public void PostExecuteInstruction(Instruction instruction) + { + } + } +} diff --git a/src/Plugins/RpcServer/Result.cs b/src/Plugins/RpcServer/Result.cs new file mode 100644 index 0000000000..9c7ace227c --- /dev/null +++ b/src/Plugins/RpcServer/Result.cs @@ -0,0 +1,116 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Result.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +namespace Neo.Plugins.RpcServer +{ + public static class Result + { + /// + /// Checks the execution result of a function and throws an exception if it is null or throw an exception. + /// + /// The function to execute + /// The rpc error + /// Append extra base exception message + /// The return type + /// The execution result + /// The Rpc exception + public static T Ok_Or(this Func function, RpcError err, bool withData = false) + { + try + { + var result = function(); + if (result == null) throw new RpcException(err); + return result; + } + catch (Exception ex) + { + if (withData) + throw new RpcException(err.WithData(ex.GetBaseException().Message)); + throw new RpcException(err); + } + } + + /// + /// Checks the execution result and throws an exception if it is null. + /// + /// The execution result + /// The rpc error + /// The return type + /// The execution result + /// The Rpc exception + public static T NotNull_Or(this T result, RpcError err) + { + if (result == null) throw new RpcException(err); + return result; + } + + /// + /// The execution result is true or throws an exception or null. + /// + /// The function to execute + /// the rpc exception code + /// the execution result + /// The rpc exception + public static bool True_Or(Func function, RpcError err) + { + try + { + var result = function(); + if (!result.Equals(true)) throw new RpcException(err); + return result; + } + catch + { + throw new RpcException(err); + } + } + + /// + /// Checks if the execution result is true or throws an exception. + /// + /// the execution result + /// the rpc exception code + /// the execution result + /// The rpc exception + public static bool True_Or(this bool result, RpcError err) + { + if (!result.Equals(true)) throw new RpcException(err); + return result; + } + + /// + /// Checks if the execution result is false or throws an exception. + /// + /// the execution result + /// the rpc exception code + /// the execution result + /// The rpc exception + public static bool False_Or(this bool result, RpcError err) + { + if (!result.Equals(false)) throw new RpcException(err); + return result; + } + + /// + /// Check if the execution result is null or throws an exception. + /// + /// The execution result + /// the rpc error + /// The execution result type + /// The execution result + /// the rpc exception + public static void Null_Or(this T result, RpcError err) + { + if (result != null) throw new RpcException(err); + } + } +} diff --git a/src/Plugins/RpcServer/RpcError.cs b/src/Plugins/RpcServer/RpcError.cs new file mode 100644 index 0000000000..667a3906d4 --- /dev/null +++ b/src/Plugins/RpcServer/RpcError.cs @@ -0,0 +1,103 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcError.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; + +namespace Neo.Plugins.RpcServer +{ + public class RpcError + { + #region Default Values + + // https://www.jsonrpc.org/specification + // | code | message | meaning | + // |--------------------|-----------------|-----------------------------------------------------------------------------------| + // | -32700 | Parse error | Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text. | + // | -32600 | Invalid request | The JSON sent is not a valid Request object. | + // | -32601 | Method not found| The method does not exist / is not available. | + // | -32602 | Invalid params | Invalid method parameter(s). | + // | -32603 | Internal error | Internal JSON-RPC error. | + // | -32000 to -32099 | Server error | Reserved for implementation-defined server-errors. | + public static readonly RpcError InvalidRequest = new(-32600, "Invalid request"); + public static readonly RpcError MethodNotFound = new(-32601, "Method not found"); + public static readonly RpcError InvalidParams = new(-32602, "Invalid params"); + public static readonly RpcError InternalServerError = new(-32603, "Internal server RpcError"); + public static readonly RpcError BadRequest = new(-32700, "Bad request"); + + // https://github.com/neo-project/proposals/pull/156/files + public static readonly RpcError UnknownBlock = new(-101, "Unknown block"); + public static readonly RpcError UnknownContract = new(-102, "Unknown contract"); + public static readonly RpcError UnknownTransaction = new(-103, "Unknown transaction"); + public static readonly RpcError UnknownStorageItem = new(-104, "Unknown storage item"); + public static readonly RpcError UnknownScriptContainer = new(-105, "Unknown script container"); + public static readonly RpcError UnknownStateRoot = new(-106, "Unknown state root"); + public static readonly RpcError UnknownSession = new(-107, "Unknown session"); + public static readonly RpcError UnknownIterator = new(-108, "Unknown iterator"); + public static readonly RpcError UnknownHeight = new(-109, "Unknown height"); + + public static readonly RpcError InsufficientFundsWallet = new(-300, "Insufficient funds in wallet"); + public static readonly RpcError WalletFeeLimit = new(-301, "Wallet fee limit exceeded", "The necessary fee is more than the Max_fee, this transaction is failed. Please increase your Max_fee value."); + public static readonly RpcError NoOpenedWallet = new(-302, "No opened wallet"); + public static readonly RpcError WalletNotFound = new(-303, "Wallet not found"); + public static readonly RpcError WalletNotSupported = new(-304, "Wallet not supported"); + + public static readonly RpcError VerificationFailed = new(-500, "Inventory verification failed"); + public static readonly RpcError AlreadyExists = new(-501, "Inventory already exists"); + public static readonly RpcError MempoolCapReached = new(-502, "Memory pool capacity reached"); + public static readonly RpcError AlreadyInPool = new(-503, "Already in pool"); + public static readonly RpcError InsufficientNetworkFee = new(-504, "Insufficient network fee"); + public static readonly RpcError PolicyFailed = new(-505, "Policy check failed"); + public static readonly RpcError InvalidScript = new(-509, "Invalid transaction script"); + public static readonly RpcError InvalidAttribute = new(-507, "Invalid transaction attribute"); + public static readonly RpcError InvalidSignature = new(-508, "Invalid signature"); + public static readonly RpcError InvalidSize = new(-509, "Invalid inventory size"); + public static readonly RpcError ExpiredTransaction = new(-510, "Expired transaction"); + public static readonly RpcError InsufficientFunds = new(-511, "Insufficient funds for fee"); + public static readonly RpcError InvalidContractVerification = new(-512, "Invalid contract verification function"); + + public static readonly RpcError AccessDenied = new(-600, "Access denied"); + public static readonly RpcError SessionsDisabled = new(-601, "State iterator sessions disabled"); + public static readonly RpcError OracleDisabled = new(-602, "Oracle service disabled"); + public static readonly RpcError OracleRequestFinished = new(-603, "Oracle request already finished"); + public static readonly RpcError OracleRequestNotFound = new(-604, "Oracle request not found"); + public static readonly RpcError OracleNotDesignatedNode = new(-605, "Not a designated oracle node"); + public static readonly RpcError UnsupportedState = new(-606, "Old state not supported"); + public static readonly RpcError InvalidProof = new(-607, "Invalid state proof"); + public static readonly RpcError ExecutionFailed = new(-608, "Contract execution failed"); + + #endregion + + public int Code { get; set; } + public string Message { get; set; } + public string Data { get; set; } + + public RpcError(int code, string message, string data = null) + { + Code = code; + Message = message; + Data = data; + } + + public override string ToString() => string.IsNullOrEmpty(Data) ? $"{Message} ({Code})" : $"{Message} ({Code}) - {Data}"; + + public JToken ToJson() + { + JObject json = new(); + json["code"] = Code; + json["message"] = ErrorMessage; + if (!string.IsNullOrEmpty(Data)) + json["data"] = Data; + return json; + } + + public string ErrorMessage => string.IsNullOrEmpty(Data) ? $"{Message}" : $"{Message} - {Data}"; + } +} diff --git a/src/Plugins/RpcServer/RpcErrorFactory.cs b/src/Plugins/RpcServer/RpcErrorFactory.cs new file mode 100644 index 0000000000..3d2ac7c9a5 --- /dev/null +++ b/src/Plugins/RpcServer/RpcErrorFactory.cs @@ -0,0 +1,43 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcErrorFactory.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Cryptography.ECC; + +namespace Neo.Plugins.RpcServer +{ + public static class RpcErrorFactory + { + public static RpcError WithData(this RpcError error, string data = null) + { + return new RpcError(error.Code, error.Message, data); + } + + public static RpcError NewCustomError(int code, string message, string data = null) + { + return new RpcError(code, message, data); + } + + #region Require data + + public static RpcError MethodNotFound(string method) => RpcError.MethodNotFound.WithData($"The method '{method}' doesn't exists."); + public static RpcError AlreadyExists(string data) => RpcError.AlreadyExists.WithData(data); + public static RpcError InvalidParams(string data) => RpcError.InvalidParams.WithData(data); + public static RpcError BadRequest(string data) => RpcError.BadRequest.WithData(data); + public static RpcError InsufficientFundsWallet(string data) => RpcError.InsufficientFundsWallet.WithData(data); + public static RpcError VerificationFailed(string data) => RpcError.VerificationFailed.WithData(data); + public static RpcError InvalidContractVerification(UInt160 contractHash) => RpcError.InvalidContractVerification.WithData($"The smart contract {contractHash} haven't got verify method."); + public static RpcError InvalidContractVerification(string data) => RpcError.InvalidContractVerification.WithData(data); + public static RpcError InvalidSignature(string data) => RpcError.InvalidSignature.WithData(data); + public static RpcError OracleNotDesignatedNode(ECPoint oraclePub) => RpcError.OracleNotDesignatedNode.WithData($"{oraclePub} isn't an oracle node."); + + #endregion + } +} diff --git a/src/Plugins/RpcServer/RpcException.cs b/src/Plugins/RpcServer/RpcException.cs new file mode 100644 index 0000000000..ab47901b6d --- /dev/null +++ b/src/Plugins/RpcServer/RpcException.cs @@ -0,0 +1,23 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcException.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; + +namespace Neo.Plugins.RpcServer +{ + public class RpcException : Exception + { + public RpcException(RpcError error) : base(error.ErrorMessage) + { + HResult = error.Code; + } + } +} diff --git a/src/Plugins/RpcServer/RpcMethodAttribute.cs b/src/Plugins/RpcServer/RpcMethodAttribute.cs new file mode 100644 index 0000000000..743530ca83 --- /dev/null +++ b/src/Plugins/RpcServer/RpcMethodAttribute.cs @@ -0,0 +1,21 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcMethodAttribute.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; + +namespace Neo.Plugins.RpcServer +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public class RpcMethodAttribute : Attribute + { + public string Name { get; set; } + } +} diff --git a/src/Plugins/RpcServer/RpcServer.Blockchain.cs b/src/Plugins/RpcServer/RpcServer.Blockchain.cs new file mode 100644 index 0000000000..1c8eecb99f --- /dev/null +++ b/src/Plugins/RpcServer/RpcServer.Blockchain.cs @@ -0,0 +1,337 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcServer.Blockchain.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using Neo.Json; +using Neo.Network.P2P.Payloads; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.VM.Types; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Neo.Plugins.RpcServer +{ + partial class RpcServer + { + [RpcMethod] + protected virtual JToken GetBestBlockHash(JArray _params) + { + return NativeContract.Ledger.CurrentHash(system.StoreView).ToString(); + } + + [RpcMethod] + protected virtual JToken GetBlock(JArray _params) + { + JToken key = Result.Ok_Or(() => _params[0], RpcError.InvalidParams.WithData($"Invalid Block Hash or Index: {_params[0]}")); + bool verbose = _params.Count >= 2 && _params[1].AsBoolean(); + using var snapshot = system.GetSnapshot(); + Block block; + if (key is JNumber) + { + uint index = uint.Parse(key.AsString()); + block = NativeContract.Ledger.GetBlock(snapshot, index); + } + else + { + UInt256 hash = UInt256.Parse(key.AsString()); + block = NativeContract.Ledger.GetBlock(snapshot, hash); + } + block.NotNull_Or(RpcError.UnknownBlock); + if (verbose) + { + JObject json = Utility.BlockToJson(block, system.Settings); + json["confirmations"] = NativeContract.Ledger.CurrentIndex(snapshot) - block.Index + 1; + UInt256 hash = NativeContract.Ledger.GetBlockHash(snapshot, block.Index + 1); + if (hash != null) + json["nextblockhash"] = hash.ToString(); + return json; + } + return Convert.ToBase64String(block.ToArray()); + } + + [RpcMethod] + internal virtual JToken GetBlockHeaderCount(JArray _params) + { + return (system.HeaderCache.Last?.Index ?? NativeContract.Ledger.CurrentIndex(system.StoreView)) + 1; + } + + [RpcMethod] + protected virtual JToken GetBlockCount(JArray _params) + { + return NativeContract.Ledger.CurrentIndex(system.StoreView) + 1; + } + + [RpcMethod] + protected virtual JToken GetBlockHash(JArray _params) + { + uint height = Result.Ok_Or(() => uint.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid Height: {_params[0]}")); + var snapshot = system.StoreView; + if (height <= NativeContract.Ledger.CurrentIndex(snapshot)) + { + return NativeContract.Ledger.GetBlockHash(snapshot, height).ToString(); + } + throw new RpcException(RpcError.UnknownHeight); + } + + [RpcMethod] + protected virtual JToken GetBlockHeader(JArray _params) + { + JToken key = _params[0]; + bool verbose = _params.Count >= 2 && _params[1].AsBoolean(); + var snapshot = system.StoreView; + Header header; + if (key is JNumber) + { + uint height = uint.Parse(key.AsString()); + header = NativeContract.Ledger.GetHeader(snapshot, height).NotNull_Or(RpcError.UnknownBlock); + } + else + { + UInt256 hash = UInt256.Parse(key.AsString()); + header = NativeContract.Ledger.GetHeader(snapshot, hash).NotNull_Or(RpcError.UnknownBlock); + } + if (verbose) + { + JObject json = header.ToJson(system.Settings); + json["confirmations"] = NativeContract.Ledger.CurrentIndex(snapshot) - header.Index + 1; + UInt256 hash = NativeContract.Ledger.GetBlockHash(snapshot, header.Index + 1); + if (hash != null) + json["nextblockhash"] = hash.ToString(); + return json; + } + + return Convert.ToBase64String(header.ToArray()); + } + + [RpcMethod] + protected virtual JToken GetContractState(JArray _params) + { + if (int.TryParse(_params[0].AsString(), out int contractId)) + { + var contractState = NativeContract.ContractManagement.GetContractById(system.StoreView, contractId); + return contractState.NotNull_Or(RpcError.UnknownContract).ToJson(); + } + + var scriptHash = ToScriptHash(_params[0].AsString()); + var contract = NativeContract.ContractManagement.GetContract(system.StoreView, scriptHash); + return contract.NotNull_Or(RpcError.UnknownContract).ToJson(); + } + + private static UInt160 ToScriptHash(string keyword) + { + foreach (var native in NativeContract.Contracts) + { + if (keyword.Equals(native.Name, StringComparison.InvariantCultureIgnoreCase) || keyword == native.Id.ToString()) + return native.Hash; + } + + return UInt160.Parse(keyword); + } + + [RpcMethod] + protected virtual JToken GetRawMemPool(JArray _params) + { + bool shouldGetUnverified = _params.Count >= 1 && _params[0].AsBoolean(); + if (!shouldGetUnverified) + return new JArray(system.MemPool.GetVerifiedTransactions().Select(p => (JToken)p.Hash.ToString())); + + JObject json = new(); + json["height"] = NativeContract.Ledger.CurrentIndex(system.StoreView); + system.MemPool.GetVerifiedAndUnverifiedTransactions( + out IEnumerable verifiedTransactions, + out IEnumerable unverifiedTransactions); + json["verified"] = new JArray(verifiedTransactions.Select(p => (JToken)p.Hash.ToString())); + json["unverified"] = new JArray(unverifiedTransactions.Select(p => (JToken)p.Hash.ToString())); + return json; + } + + [RpcMethod] + protected virtual JToken GetRawTransaction(JArray _params) + { + UInt256 hash = Result.Ok_Or(() => UInt256.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid Transaction Hash: {_params[0]}")); + bool verbose = _params.Count >= 2 && _params[1].AsBoolean(); + if (system.MemPool.TryGetValue(hash, out Transaction tx) && !verbose) + return Convert.ToBase64String(tx.ToArray()); + var snapshot = system.StoreView; + TransactionState state = NativeContract.Ledger.GetTransactionState(snapshot, hash); + tx ??= state?.Transaction; + tx.NotNull_Or(RpcError.UnknownTransaction); + if (!verbose) return Convert.ToBase64String(tx.ToArray()); + JObject json = Utility.TransactionToJson(tx, system.Settings); + if (state is not null) + { + TrimmedBlock block = NativeContract.Ledger.GetTrimmedBlock(snapshot, NativeContract.Ledger.GetBlockHash(snapshot, state.BlockIndex)); + json["blockhash"] = block.Hash.ToString(); + json["confirmations"] = NativeContract.Ledger.CurrentIndex(snapshot) - block.Index + 1; + json["blocktime"] = block.Header.Timestamp; + } + return json; + } + + [RpcMethod] + protected virtual JToken GetStorage(JArray _params) + { + using var snapshot = system.GetSnapshot(); + if (!int.TryParse(_params[0].AsString(), out int id)) + { + UInt160 hash = UInt160.Parse(_params[0].AsString()); + ContractState contract = NativeContract.ContractManagement.GetContract(snapshot, hash).NotNull_Or(RpcError.UnknownContract); + id = contract.Id; + } + byte[] key = Convert.FromBase64String(_params[1].AsString()); + StorageItem item = snapshot.TryGet(new StorageKey + { + Id = id, + Key = key + }).NotNull_Or(RpcError.UnknownStorageItem); + return Convert.ToBase64String(item.Value.Span); + } + + [RpcMethod] + protected virtual JToken FindStorage(JArray _params) + { + using var snapshot = system.GetSnapshot(); + if (!int.TryParse(_params[0].AsString(), out int id)) + { + UInt160 hash = UInt160.Parse(_params[0].AsString()); + ContractState contract = NativeContract.ContractManagement.GetContract(snapshot, hash).NotNull_Or(RpcError.UnknownContract); + id = contract.Id; + } + + byte[] prefix = Convert.FromBase64String(_params[1].AsString()); + byte[] prefix_key = StorageKey.CreateSearchPrefix(id, prefix); + + if (!int.TryParse(_params[2].AsString(), out int start)) + { + start = 0; + } + + JObject json = new(); + JArray jarr = new(); + int pageSize = settings.FindStoragePageSize; + int i = 0; + + using (var iter = snapshot.Find(prefix_key).Skip(count: start).GetEnumerator()) + { + var hasMore = false; + while (iter.MoveNext()) + { + if (i == pageSize) + { + hasMore = true; + break; + } + + JObject j = new(); + j["key"] = Convert.ToBase64String(iter.Current.Key.Key.Span); + j["value"] = Convert.ToBase64String(iter.Current.Value.Value.Span); + jarr.Add(j); + i++; + } + json["truncated"] = hasMore; + } + + json["next"] = start + i; + json["results"] = jarr; + return json; + } + + [RpcMethod] + protected virtual JToken GetTransactionHeight(JArray _params) + { + UInt256 hash = Result.Ok_Or(() => UInt256.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid Transaction Hash: {_params[0]}")); + uint? height = NativeContract.Ledger.GetTransactionState(system.StoreView, hash)?.BlockIndex; + if (height.HasValue) return height.Value; + throw new RpcException(RpcError.UnknownTransaction); + } + + [RpcMethod] + protected virtual JToken GetNextBlockValidators(JArray _params) + { + using var snapshot = system.GetSnapshot(); + var validators = NativeContract.NEO.GetNextBlockValidators(snapshot, system.Settings.ValidatorsCount); + return validators.Select(p => + { + JObject validator = new(); + validator["publickey"] = p.ToString(); + validator["votes"] = (int)NativeContract.NEO.GetCandidateVote(snapshot, p); + return validator; + }).ToArray(); + } + + [RpcMethod] + protected virtual JToken GetCandidates(JArray _params) + { + using var snapshot = system.GetSnapshot(); + byte[] script; + using (ScriptBuilder sb = new()) + { + script = sb.EmitDynamicCall(NativeContract.NEO.Hash, "getCandidates", null).ToArray(); + } + StackItem[] resultstack; + try + { + using ApplicationEngine engine = ApplicationEngine.Run(script, snapshot, settings: system.Settings, gas: settings.MaxGasInvoke); + resultstack = engine.ResultStack.ToArray(); + } + catch + { + throw new RpcException(RpcError.InternalServerError.WithData("Can't get candidates.")); + } + + JObject json = new(); + try + { + if (resultstack.Length > 0) + { + JArray jArray = new(); + var validators = NativeContract.NEO.GetNextBlockValidators(snapshot, system.Settings.ValidatorsCount) ?? throw new RpcException(RpcError.InternalServerError.WithData("Can't get next block validators.")); + + foreach (var item in resultstack) + { + var value = (VM.Types.Array)item; + foreach (Struct ele in value) + { + var publickey = ele[0].GetSpan().ToHexString(); + json["publickey"] = publickey; + json["votes"] = ele[1].GetInteger().ToString(); + json["active"] = validators.ToByteArray().ToHexString().Contains(publickey); + jArray.Add(json); + json = new(); + } + return jArray; + } + } + } + catch + { + throw new RpcException(RpcError.InternalServerError.WithData("Can't get next block validators")); + } + + return json; + } + + [RpcMethod] + protected virtual JToken GetCommittee(JArray _params) + { + return new JArray(NativeContract.NEO.GetCommittee(system.StoreView).Select(p => (JToken)p.ToString())); + } + + [RpcMethod] + protected virtual JToken GetNativeContracts(JArray _params) + { + return new JArray(NativeContract.Contracts.Select(p => NativeContract.ContractManagement.GetContract(system.StoreView, p.Hash).ToJson())); + } + } +} diff --git a/src/Plugins/RpcServer/RpcServer.Node.cs b/src/Plugins/RpcServer/RpcServer.Node.cs new file mode 100644 index 0000000000..79a8884a0f --- /dev/null +++ b/src/Plugins/RpcServer/RpcServer.Node.cs @@ -0,0 +1,168 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcServer.Node.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Akka.Actor; +using Neo.IO; +using Neo.Json; +using Neo.Ledger; +using Neo.Network.P2P; +using Neo.Network.P2P.Payloads; +using System; +using System.Linq; +using static Neo.Ledger.Blockchain; + +namespace Neo.Plugins.RpcServer +{ + partial class RpcServer + { + [RpcMethod] + protected virtual JToken GetConnectionCount(JArray _params) + { + return localNode.ConnectedCount; + } + + [RpcMethod] + protected virtual JToken GetPeers(JArray _params) + { + JObject json = new(); + json["unconnected"] = new JArray(localNode.GetUnconnectedPeers().Select(p => + { + JObject peerJson = new(); + peerJson["address"] = p.Address.ToString(); + peerJson["port"] = p.Port; + return peerJson; + })); + json["bad"] = new JArray(); //badpeers has been removed + json["connected"] = new JArray(localNode.GetRemoteNodes().Select(p => + { + JObject peerJson = new(); + peerJson["address"] = p.Remote.Address.ToString(); + peerJson["port"] = p.ListenerTcpPort; + return peerJson; + })); + return json; + } + + private static JObject GetRelayResult(VerifyResult reason, UInt256 hash) + { + + switch (reason) + { + case VerifyResult.Succeed: + { + var ret = new JObject(); + ret["hash"] = hash.ToString(); + return ret; + } + case VerifyResult.AlreadyExists: + { + throw new RpcException(RpcError.AlreadyExists.WithData(reason.ToString())); + } + case VerifyResult.AlreadyInPool: + { + throw new RpcException(RpcError.AlreadyInPool.WithData(reason.ToString())); + } + case VerifyResult.OutOfMemory: + { + throw new RpcException(RpcError.MempoolCapReached.WithData(reason.ToString())); + } + case VerifyResult.InvalidScript: + { + throw new RpcException(RpcError.InvalidScript.WithData(reason.ToString())); + } + case VerifyResult.InvalidAttribute: + { + throw new RpcException(RpcError.InvalidAttribute.WithData(reason.ToString())); + } + case VerifyResult.InvalidSignature: + { + throw new RpcException(RpcError.InvalidSignature.WithData(reason.ToString())); + } + case VerifyResult.OverSize: + { + throw new RpcException(RpcError.InvalidSize.WithData(reason.ToString())); + } + case VerifyResult.Expired: + { + throw new RpcException(RpcError.ExpiredTransaction.WithData(reason.ToString())); + } + case VerifyResult.InsufficientFunds: + { + throw new RpcException(RpcError.InsufficientFunds.WithData(reason.ToString())); + } + case VerifyResult.PolicyFail: + { + throw new RpcException(RpcError.PolicyFailed.WithData(reason.ToString())); + } + default: + { + throw new RpcException(RpcError.VerificationFailed.WithData(reason.ToString())); + } + } + } + + [RpcMethod] + protected virtual JToken GetVersion(JArray _params) + { + JObject json = new(); + json["tcpport"] = localNode.ListenerTcpPort; + json["nonce"] = LocalNode.Nonce; + json["useragent"] = LocalNode.UserAgent; + // rpc settings + JObject rpc = new(); + rpc["maxiteratorresultitems"] = settings.MaxIteratorResultItems; + rpc["sessionenabled"] = settings.SessionEnabled; + // protocol settings + JObject protocol = new(); + protocol["addressversion"] = system.Settings.AddressVersion; + protocol["network"] = system.Settings.Network; + protocol["validatorscount"] = system.Settings.ValidatorsCount; + protocol["msperblock"] = system.Settings.MillisecondsPerBlock; + protocol["maxtraceableblocks"] = system.Settings.MaxTraceableBlocks; + protocol["maxvaliduntilblockincrement"] = system.Settings.MaxValidUntilBlockIncrement; + protocol["maxtransactionsperblock"] = system.Settings.MaxTransactionsPerBlock; + protocol["memorypoolmaxtransactions"] = system.Settings.MemoryPoolMaxTransactions; + protocol["initialgasdistribution"] = system.Settings.InitialGasDistribution; + protocol["hardforks"] = new JArray(system.Settings.Hardforks.Select(hf => + { + JObject forkJson = new(); + // Strip "HF_" prefix. + forkJson["name"] = StripPrefix(hf.Key.ToString(), "HF_"); + forkJson["blockheight"] = hf.Value; + return forkJson; + })); + json["rpc"] = rpc; + json["protocol"] = protocol; + return json; + } + + private static string StripPrefix(string s, string prefix) + { + return s.StartsWith(prefix) ? s.Substring(prefix.Length) : s; + } + + [RpcMethod] + protected virtual JToken SendRawTransaction(JArray _params) + { + Transaction tx = Result.Ok_Or(() => Convert.FromBase64String(_params[0].AsString()).AsSerializable(), RpcError.InvalidParams.WithData($"Invalid Transaction Format: {_params[0]}")); + RelayResult reason = system.Blockchain.Ask(tx).Result; + return GetRelayResult(reason.Result, tx.Hash); + } + + [RpcMethod] + protected virtual JToken SubmitBlock(JArray _params) + { + Block block = Result.Ok_Or(() => Convert.FromBase64String(_params[0].AsString()).AsSerializable(), RpcError.InvalidParams.WithData($"Invalid Block Format: {_params[0]}")); + RelayResult reason = system.Blockchain.Ask(block).Result; + return GetRelayResult(reason.Result, block.Hash); + } + } +} diff --git a/src/Plugins/RpcServer/RpcServer.SmartContract.cs b/src/Plugins/RpcServer/RpcServer.SmartContract.cs new file mode 100644 index 0000000000..2fe49d4965 --- /dev/null +++ b/src/Plugins/RpcServer/RpcServer.SmartContract.cs @@ -0,0 +1,298 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcServer.SmartContract.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Cryptography.ECC; +using Neo.IO; +using Neo.Json; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Iterators; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.VM.Types; +using Neo.Wallets; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Array = System.Array; + +namespace Neo.Plugins.RpcServer +{ + partial class RpcServer + { + private readonly Dictionary sessions = new(); + private Timer timer; + + private void Initialize_SmartContract() + { + if (settings.SessionEnabled) + timer = new(OnTimer, null, settings.SessionExpirationTime, settings.SessionExpirationTime); + } + + private void Dispose_SmartContract() + { + timer?.Dispose(); + Session[] toBeDestroyed; + lock (sessions) + { + toBeDestroyed = sessions.Values.ToArray(); + sessions.Clear(); + } + foreach (Session session in toBeDestroyed) + session.Dispose(); + } + + private void OnTimer(object state) + { + List<(Guid Id, Session Session)> toBeDestroyed = new(); + lock (sessions) + { + foreach (var (id, session) in sessions) + if (DateTime.UtcNow >= session.StartTime + settings.SessionExpirationTime) + toBeDestroyed.Add((id, session)); + foreach (var (id, _) in toBeDestroyed) + sessions.Remove(id); + } + foreach (var (_, session) in toBeDestroyed) + session.Dispose(); + } + + private JObject GetInvokeResult(byte[] script, Signer[] signers = null, Witness[] witnesses = null, bool useDiagnostic = false) + { + JObject json = new(); + Session session = new(system, script, signers, witnesses, settings.MaxGasInvoke, useDiagnostic ? new Diagnostic() : null); + try + { + json["script"] = Convert.ToBase64String(script); + json["state"] = session.Engine.State; + // Gas consumed in the unit of datoshi, 1 GAS = 10^8 datoshi + json["gasconsumed"] = session.Engine.FeeConsumed.ToString(); + json["exception"] = GetExceptionMessage(session.Engine.FaultException); + json["notifications"] = new JArray(session.Engine.Notifications.Select(n => + { + var obj = new JObject(); + obj["eventname"] = n.EventName; + obj["contract"] = n.ScriptHash.ToString(); + obj["state"] = ToJson(n.State, session); + return obj; + })); + if (useDiagnostic) + { + Diagnostic diagnostic = (Diagnostic)session.Engine.Diagnostic; + json["diagnostics"] = new JObject() + { + ["invokedcontracts"] = ToJson(diagnostic.InvocationTree.Root), + ["storagechanges"] = ToJson(session.Engine.Snapshot.GetChangeSet()) + }; + } + var stack = new JArray(); + foreach (var item in session.Engine.ResultStack) + { + try + { + stack.Add(ToJson(item, session)); + } + catch (Exception ex) + { + stack.Add("error: " + ex.Message); + } + } + json["stack"] = stack; + if (session.Engine.State != VMState.FAULT) + { + ProcessInvokeWithWallet(json, signers); + } + } + catch + { + session.Dispose(); + throw; + } + if (session.Iterators.Count == 0 || !settings.SessionEnabled) + { + session.Dispose(); + } + else + { + Guid id = Guid.NewGuid(); + json["session"] = id.ToString(); + lock (sessions) + sessions.Add(id, session); + } + return json; + } + + private static JObject ToJson(TreeNode node) + { + JObject json = new(); + json["hash"] = node.Item.ToString(); + if (node.Children.Any()) + { + json["call"] = new JArray(node.Children.Select(ToJson)); + } + return json; + } + + private static JArray ToJson(IEnumerable changes) + { + JArray array = new(); + foreach (var entry in changes) + { + array.Add(new JObject + { + ["state"] = entry.State.ToString(), + ["key"] = Convert.ToBase64String(entry.Key.ToArray()), + ["value"] = Convert.ToBase64String(entry.Item.Value.ToArray()) + }); + } + return array; + } + + private static JObject ToJson(StackItem item, Session session) + { + JObject json = item.ToJson(); + if (item is InteropInterface interopInterface && interopInterface.GetInterface() is IIterator iterator) + { + Guid id = Guid.NewGuid(); + session.Iterators.Add(id, iterator); + json["interface"] = nameof(IIterator); + json["id"] = id.ToString(); + } + return json; + } + + private static Signer[] SignersFromJson(JArray _params, ProtocolSettings settings) + { + if (_params.Count > Transaction.MaxTransactionAttributes) + { + throw new RpcException(RpcError.InvalidParams.WithData("Max allowed witness exceeded.")); + } + + var ret = _params.Select(u => new Signer + { + Account = AddressToScriptHash(u["account"].AsString(), settings.AddressVersion), + Scopes = (WitnessScope)Enum.Parse(typeof(WitnessScope), u["scopes"]?.AsString()), + AllowedContracts = ((JArray)u["allowedcontracts"])?.Select(p => UInt160.Parse(p.AsString())).ToArray() ?? Array.Empty(), + AllowedGroups = ((JArray)u["allowedgroups"])?.Select(p => ECPoint.Parse(p.AsString(), ECCurve.Secp256r1)).ToArray() ?? Array.Empty(), + Rules = ((JArray)u["rules"])?.Select(r => WitnessRule.FromJson((JObject)r)).ToArray() ?? Array.Empty(), + }).ToArray(); + + // Validate format + + _ = IO.Helper.ToByteArray(ret).AsSerializableArray(); + + return ret; + } + + private static Witness[] WitnessesFromJson(JArray _params) + { + if (_params.Count > Transaction.MaxTransactionAttributes) + { + throw new RpcException(RpcError.InvalidParams.WithData("Max allowed witness exceeded.")); + } + + return _params.Select(u => new + { + Invocation = u["invocation"]?.AsString(), + Verification = u["verification"]?.AsString() + }).Where(x => x.Invocation != null || x.Verification != null).Select(x => new Witness() + { + InvocationScript = Convert.FromBase64String(x.Invocation ?? string.Empty), + VerificationScript = Convert.FromBase64String(x.Verification ?? string.Empty) + }).ToArray(); + } + + [RpcMethod] + protected virtual JToken InvokeFunction(JArray _params) + { + UInt160 script_hash = Result.Ok_Or(() => UInt160.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid script hash {nameof(script_hash)}")); + string operation = Result.Ok_Or(() => _params[1].AsString(), RpcError.InvalidParams); + ContractParameter[] args = _params.Count >= 3 ? ((JArray)_params[2]).Select(p => ContractParameter.FromJson((JObject)p)).ToArray() : System.Array.Empty(); + Signer[] signers = _params.Count >= 4 ? SignersFromJson((JArray)_params[3], system.Settings) : null; + Witness[] witnesses = _params.Count >= 4 ? WitnessesFromJson((JArray)_params[3]) : null; + bool useDiagnostic = _params.Count >= 5 && _params[4].GetBoolean(); + + byte[] script; + using (ScriptBuilder sb = new()) + { + script = sb.EmitDynamicCall(script_hash, operation, args).ToArray(); + } + return GetInvokeResult(script, signers, witnesses, useDiagnostic); + } + + [RpcMethod] + protected virtual JToken InvokeScript(JArray _params) + { + byte[] script = Result.Ok_Or(() => Convert.FromBase64String(_params[0].AsString()), RpcError.InvalidParams); + Signer[] signers = _params.Count >= 2 ? SignersFromJson((JArray)_params[1], system.Settings) : null; + Witness[] witnesses = _params.Count >= 2 ? WitnessesFromJson((JArray)_params[1]) : null; + bool useDiagnostic = _params.Count >= 3 && _params[2].GetBoolean(); + return GetInvokeResult(script, signers, witnesses, useDiagnostic); + } + + [RpcMethod] + protected virtual JToken TraverseIterator(JArray _params) + { + settings.SessionEnabled.True_Or(RpcError.SessionsDisabled); + Guid sid = Result.Ok_Or(() => Guid.Parse(_params[0].GetString()), RpcError.InvalidParams.WithData($"Invalid session id {nameof(sid)}")); + Guid iid = Result.Ok_Or(() => Guid.Parse(_params[1].GetString()), RpcError.InvalidParams.WithData($"Invliad iterator id {nameof(iid)}")); + int count = _params[2].GetInt32(); + Result.True_Or(() => count <= settings.MaxIteratorResultItems, RpcError.InvalidParams.WithData($"Invalid iterator items count {nameof(count)}")); + Session session; + lock (sessions) + { + session = Result.Ok_Or(() => sessions[sid], RpcError.UnknownSession); + session.ResetExpiration(); + } + IIterator iterator = Result.Ok_Or(() => session.Iterators[iid], RpcError.UnknownIterator); + JArray json = new(); + while (count-- > 0 && iterator.Next()) + json.Add(iterator.Value(null).ToJson()); + return json; + } + + [RpcMethod] + protected virtual JToken TerminateSession(JArray _params) + { + settings.SessionEnabled.True_Or(RpcError.SessionsDisabled); + Guid sid = Result.Ok_Or(() => Guid.Parse(_params[0].GetString()), RpcError.InvalidParams.WithData("Invalid session id")); + + Session session = null; + bool result; + lock (sessions) + { + result = Result.Ok_Or(() => sessions.Remove(sid, out session), RpcError.UnknownSession); + } + if (result) session.Dispose(); + return result; + } + + [RpcMethod] + protected virtual JToken GetUnclaimedGas(JArray _params) + { + string address = Result.Ok_Or(() => _params[0].AsString(), RpcError.InvalidParams.WithData($"Invalid address {nameof(address)}")); + JObject json = new(); + UInt160 script_hash = Result.Ok_Or(() => AddressToScriptHash(address, system.Settings.AddressVersion), RpcError.InvalidParams); + + var snapshot = system.StoreView; + json["unclaimed"] = NativeContract.NEO.UnclaimedGas(snapshot, script_hash, NativeContract.Ledger.CurrentIndex(snapshot) + 1).ToString(); + json["address"] = script_hash.ToAddress(system.Settings.AddressVersion); + return json; + } + + static string GetExceptionMessage(Exception exception) + { + return exception?.GetBaseException().Message; + } + } +} diff --git a/src/Plugins/RpcServer/RpcServer.Utilities.cs b/src/Plugins/RpcServer/RpcServer.Utilities.cs new file mode 100644 index 0000000000..f9b874c824 --- /dev/null +++ b/src/Plugins/RpcServer/RpcServer.Utilities.cs @@ -0,0 +1,55 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcServer.Utilities.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using Neo.Wallets; +using System.Linq; + +namespace Neo.Plugins.RpcServer +{ + partial class RpcServer + { + [RpcMethod] + protected virtual JToken ListPlugins(JArray _params) + { + return new JArray(Plugin.Plugins + .OrderBy(u => u.Name) + .Select(u => new JObject + { + ["name"] = u.Name, + ["version"] = u.Version.ToString(), + ["interfaces"] = new JArray(u.GetType().GetInterfaces() + .Select(p => p.Name) + .Where(p => p.EndsWith("Plugin")) + .Select(p => (JToken)p)) + })); + } + + [RpcMethod] + protected virtual JToken ValidateAddress(JArray _params) + { + string address = Result.Ok_Or(() => _params[0].AsString(), RpcError.InvalidParams.WithData($"Invlid address format: {_params[0]}")); + JObject json = new(); + UInt160 scriptHash; + try + { + scriptHash = address.ToScriptHash(system.Settings.AddressVersion); + } + catch + { + scriptHash = null; + } + json["address"] = address; + json["isvalid"] = scriptHash != null; + return json; + } + } +} diff --git a/src/Plugins/RpcServer/RpcServer.Wallet.cs b/src/Plugins/RpcServer/RpcServer.Wallet.cs new file mode 100644 index 0000000000..f1adb277ce --- /dev/null +++ b/src/Plugins/RpcServer/RpcServer.Wallet.cs @@ -0,0 +1,425 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcServer.Wallet.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Akka.Actor; +using Neo.IO; +using Neo.Json; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using Neo.Wallets.NEP6; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Numerics; + +namespace Neo.Plugins.RpcServer +{ + partial class RpcServer + { + private class DummyWallet : Wallet + { + public DummyWallet(ProtocolSettings settings) : base(null, settings) { } + public override string Name => ""; + public override Version Version => new(); + + public override bool ChangePassword(string oldPassword, string newPassword) => false; + public override bool Contains(UInt160 scriptHash) => false; + public override WalletAccount CreateAccount(byte[] privateKey) => null; + public override WalletAccount CreateAccount(Contract contract, KeyPair key = null) => null; + public override WalletAccount CreateAccount(UInt160 scriptHash) => null; + public override void Delete() { } + public override bool DeleteAccount(UInt160 scriptHash) => false; + public override WalletAccount GetAccount(UInt160 scriptHash) => null; + public override IEnumerable GetAccounts() => Array.Empty(); + public override bool VerifyPassword(string password) => false; + public override void Save() { } + } + + protected Wallet wallet; + + private void CheckWallet() + { + wallet.NotNull_Or(RpcError.NoOpenedWallet); + } + + [RpcMethod] + protected virtual JToken CloseWallet(JArray _params) + { + wallet = null; + return true; + } + + [RpcMethod] + protected virtual JToken DumpPrivKey(JArray _params) + { + CheckWallet(); + UInt160 scriptHash = AddressToScriptHash(_params[0].AsString(), system.Settings.AddressVersion); + WalletAccount account = wallet.GetAccount(scriptHash); + return account.GetKey().Export(); + } + + [RpcMethod] + protected virtual JToken GetNewAddress(JArray _params) + { + CheckWallet(); + WalletAccount account = wallet.CreateAccount(); + if (wallet is NEP6Wallet nep6) + nep6.Save(); + return account.Address; + } + + [RpcMethod] + protected virtual JToken GetWalletBalance(JArray _params) + { + CheckWallet(); + UInt160 asset_id = UInt160.Parse(_params[0].AsString()); + JObject json = new(); + json["balance"] = wallet.GetAvailable(system.StoreView, asset_id).Value.ToString(); + return json; + } + + [RpcMethod] + protected virtual JToken GetWalletUnclaimedGas(JArray _params) + { + CheckWallet(); + // Datoshi is the smallest unit of GAS, 1 GAS = 10^8 Datoshi + BigInteger datoshi = BigInteger.Zero; + using (var snapshot = system.GetSnapshot()) + { + uint height = NativeContract.Ledger.CurrentIndex(snapshot) + 1; + foreach (UInt160 account in wallet.GetAccounts().Select(p => p.ScriptHash)) + datoshi += NativeContract.NEO.UnclaimedGas(snapshot, account, height); + } + return datoshi.ToString(); + } + + [RpcMethod] + protected virtual JToken ImportPrivKey(JArray _params) + { + CheckWallet(); + string privkey = _params[0].AsString(); + WalletAccount account = wallet.Import(privkey); + if (wallet is NEP6Wallet nep6wallet) + nep6wallet.Save(); + return new JObject + { + ["address"] = account.Address, + ["haskey"] = account.HasKey, + ["label"] = account.Label, + ["watchonly"] = account.WatchOnly + }; + } + + [RpcMethod] + protected virtual JToken CalculateNetworkFee(JArray _params) + { + var tx = Convert.FromBase64String(_params[0].AsString()); + + JObject account = new(); + var networkfee = Wallets.Helper.CalculateNetworkFee( + tx.AsSerializable(), system.StoreView, system.Settings, + wallet is not null ? a => wallet.GetAccount(a).Contract.Script : _ => null); + account["networkfee"] = networkfee.ToString(); + return account; + } + + [RpcMethod] + protected virtual JToken ListAddress(JArray _params) + { + CheckWallet(); + return wallet.GetAccounts().Select(p => + { + JObject account = new(); + account["address"] = p.Address; + account["haskey"] = p.HasKey; + account["label"] = p.Label; + account["watchonly"] = p.WatchOnly; + return account; + }).ToArray(); + } + + [RpcMethod] + protected virtual JToken OpenWallet(JArray _params) + { + string path = _params[0].AsString(); + string password = _params[1].AsString(); + File.Exists(path).True_Or(RpcError.WalletNotFound); + wallet = Wallet.Open(path, password, system.Settings).NotNull_Or(RpcError.WalletNotSupported); + return true; + } + + private void ProcessInvokeWithWallet(JObject result, Signer[] signers = null) + { + if (wallet == null || signers == null || signers.Length == 0) return; + + UInt160 sender = signers[0].Account; + Transaction tx; + try + { + tx = wallet.MakeTransaction(system.StoreView, Convert.FromBase64String(result["script"].AsString()), sender, signers, maxGas: settings.MaxGasInvoke); + } + catch (Exception e) + { + result["exception"] = GetExceptionMessage(e); + return; + } + ContractParametersContext context = new(system.StoreView, tx, settings.Network); + wallet.Sign(context); + if (context.Completed) + { + tx.Witnesses = context.GetWitnesses(); + result["tx"] = Convert.ToBase64String(tx.ToArray()); + } + else + { + result["pendingsignature"] = context.ToJson(); + } + } + + [RpcMethod] + protected virtual JToken SendFrom(JArray _params) + { + CheckWallet(); + UInt160 assetId = UInt160.Parse(_params[0].AsString()); + UInt160 from = AddressToScriptHash(_params[1].AsString(), system.Settings.AddressVersion); + UInt160 to = AddressToScriptHash(_params[2].AsString(), system.Settings.AddressVersion); + using var snapshot = system.GetSnapshot(); + AssetDescriptor descriptor = new(snapshot, system.Settings, assetId); + BigDecimal amount = new(BigInteger.Parse(_params[3].AsString()), descriptor.Decimals); + (amount.Sign > 0).True_Or(RpcErrorFactory.InvalidParams("Amount can't be negative.")); + Signer[] signers = _params.Count >= 5 ? ((JArray)_params[4]).Select(p => new Signer() { Account = AddressToScriptHash(p.AsString(), system.Settings.AddressVersion), Scopes = WitnessScope.CalledByEntry }).ToArray() : null; + + Transaction tx = wallet.MakeTransaction(snapshot, new[] + { + new TransferOutput + { + AssetId = assetId, + Value = amount, + ScriptHash = to + } + }, from, signers).NotNull_Or(RpcError.InsufficientFunds); + + ContractParametersContext transContext = new(snapshot, tx, settings.Network); + wallet.Sign(transContext); + if (!transContext.Completed) + return transContext.ToJson(); + tx.Witnesses = transContext.GetWitnesses(); + if (tx.Size > 1024) + { + long calFee = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot) + 100000; + if (tx.NetworkFee < calFee) + tx.NetworkFee = calFee; + } + (tx.NetworkFee <= settings.MaxFee).True_Or(RpcError.WalletFeeLimit); + return SignAndRelay(snapshot, tx); + } + + [RpcMethod] + protected virtual JToken SendMany(JArray _params) + { + CheckWallet(); + int to_start = 0; + UInt160 from = null; + if (_params[0] is JString) + { + from = AddressToScriptHash(_params[0].AsString(), system.Settings.AddressVersion); + to_start = 1; + } + JArray to = Result.Ok_Or(() => (JArray)_params[to_start], RpcError.InvalidParams.WithData($"Invalid 'to' parameter: {_params[to_start]}")); + (to.Count != 0).True_Or(RpcErrorFactory.InvalidParams("Argument 'to' can't be empty.")); + Signer[] signers = _params.Count >= to_start + 2 ? ((JArray)_params[to_start + 1]).Select(p => new Signer() { Account = AddressToScriptHash(p.AsString(), system.Settings.AddressVersion), Scopes = WitnessScope.CalledByEntry }).ToArray() : null; + + TransferOutput[] outputs = new TransferOutput[to.Count]; + using var snapshot = system.GetSnapshot(); + for (int i = 0; i < to.Count; i++) + { + UInt160 asset_id = UInt160.Parse(to[i]["asset"].AsString()); + AssetDescriptor descriptor = new(snapshot, system.Settings, asset_id); + outputs[i] = new TransferOutput + { + AssetId = asset_id, + Value = new BigDecimal(BigInteger.Parse(to[i]["value"].AsString()), descriptor.Decimals), + ScriptHash = AddressToScriptHash(to[i]["address"].AsString(), system.Settings.AddressVersion) + }; + (outputs[i].Value.Sign > 0).True_Or(RpcErrorFactory.InvalidParams($"Amount of '{asset_id}' can't be negative.")); + } + Transaction tx = wallet.MakeTransaction(snapshot, outputs, from, signers).NotNull_Or(RpcError.InsufficientFunds); + + ContractParametersContext transContext = new(snapshot, tx, settings.Network); + wallet.Sign(transContext); + if (!transContext.Completed) + return transContext.ToJson(); + tx.Witnesses = transContext.GetWitnesses(); + if (tx.Size > 1024) + { + long calFee = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot) + 100000; + if (tx.NetworkFee < calFee) + tx.NetworkFee = calFee; + } + (tx.NetworkFee <= settings.MaxFee).True_Or(RpcError.WalletFeeLimit); + return SignAndRelay(snapshot, tx); + } + + [RpcMethod] + protected virtual JToken SendToAddress(JArray _params) + { + CheckWallet(); + UInt160 assetId = Result.Ok_Or(() => UInt160.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid asset hash: {_params[0]}")); + UInt160 to = AddressToScriptHash(_params[1].AsString(), system.Settings.AddressVersion); + using var snapshot = system.GetSnapshot(); + AssetDescriptor descriptor = new(snapshot, system.Settings, assetId); + BigDecimal amount = new(BigInteger.Parse(_params[2].AsString()), descriptor.Decimals); + (amount.Sign > 0).True_Or(RpcError.InvalidParams); + Transaction tx = wallet.MakeTransaction(snapshot, new[] + { + new TransferOutput + { + AssetId = assetId, + Value = amount, + ScriptHash = to + } + }).NotNull_Or(RpcError.InsufficientFunds); + + ContractParametersContext transContext = new(snapshot, tx, settings.Network); + wallet.Sign(transContext); + if (!transContext.Completed) + return transContext.ToJson(); + tx.Witnesses = transContext.GetWitnesses(); + if (tx.Size > 1024) + { + long calFee = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot) + 100000; + if (tx.NetworkFee < calFee) + tx.NetworkFee = calFee; + } + (tx.NetworkFee <= settings.MaxFee).True_Or(RpcError.WalletFeeLimit); + return SignAndRelay(snapshot, tx); + } + + [RpcMethod] + protected virtual JToken CancelTransaction(JArray _params) + { + CheckWallet(); + var txid = Result.Ok_Or(() => UInt256.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid txid: {_params[0]}")); + NativeContract.Ledger.GetTransactionState(system.StoreView, txid).Null_Or(RpcErrorFactory.AlreadyExists("This tx is already confirmed, can't be cancelled.")); + + var conflict = new TransactionAttribute[] { new Conflicts() { Hash = txid } }; + Signer[] signers = _params.Count >= 2 ? ((JArray)_params[1]).Select(j => new Signer() { Account = AddressToScriptHash(j.AsString(), system.Settings.AddressVersion), Scopes = WitnessScope.None }).ToArray() : Array.Empty(); + signers.Any().True_Or(RpcErrorFactory.BadRequest("No signer.")); + Transaction tx = new Transaction + { + Signers = signers, + Attributes = conflict, + Witnesses = Array.Empty(), + }; + + tx = Result.Ok_Or(() => wallet.MakeTransaction(system.StoreView, new[] { (byte)OpCode.RET }, signers[0].Account, signers, conflict), RpcError.InsufficientFunds, true); + + if (system.MemPool.TryGetValue(txid, out Transaction conflictTx)) + { + tx.NetworkFee = Math.Max(tx.NetworkFee, conflictTx.NetworkFee) + 1; + } + else if (_params.Count >= 3) + { + var extraFee = _params[2].AsString(); + AssetDescriptor descriptor = new(system.StoreView, system.Settings, NativeContract.GAS.Hash); + (BigDecimal.TryParse(extraFee, descriptor.Decimals, out BigDecimal decimalExtraFee) && decimalExtraFee.Sign > 0).True_Or(RpcErrorFactory.InvalidParams("Incorrect amount format.")); + + tx.NetworkFee += (long)decimalExtraFee.Value; + }; + return SignAndRelay(system.StoreView, tx); + } + + [RpcMethod] + protected virtual JToken InvokeContractVerify(JArray _params) + { + UInt160 script_hash = Result.Ok_Or(() => UInt160.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid script hash: {_params[0]}")); + ContractParameter[] args = _params.Count >= 2 ? ((JArray)_params[1]).Select(p => ContractParameter.FromJson((JObject)p)).ToArray() : Array.Empty(); + Signer[] signers = _params.Count >= 3 ? SignersFromJson((JArray)_params[2], system.Settings) : null; + Witness[] witnesses = _params.Count >= 3 ? WitnessesFromJson((JArray)_params[2]) : null; + return GetVerificationResult(script_hash, args, signers, witnesses); + } + + private JObject GetVerificationResult(UInt160 scriptHash, ContractParameter[] args, Signer[] signers = null, Witness[] witnesses = null) + { + using var snapshot = system.GetSnapshot(); + var contract = NativeContract.ContractManagement.GetContract(snapshot, scriptHash).NotNull_Or(RpcError.UnknownContract); + var md = contract.Manifest.Abi.GetMethod(ContractBasicMethod.Verify, ContractBasicMethod.VerifyPCount).NotNull_Or(RpcErrorFactory.InvalidContractVerification(contract.Hash)); + (md.ReturnType == ContractParameterType.Boolean).True_Or(RpcErrorFactory.InvalidContractVerification("The verify method doesn't return boolean value.")); + Transaction tx = new() + { + Signers = signers ?? new Signer[] { new() { Account = scriptHash } }, + Attributes = Array.Empty(), + Witnesses = witnesses, + Script = new[] { (byte)OpCode.RET } + }; + using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot.CreateSnapshot(), settings: system.Settings); + engine.LoadContract(contract, md, CallFlags.ReadOnly); + + var invocationScript = Array.Empty(); + if (args.Length > 0) + { + using ScriptBuilder sb = new(); + for (int i = args.Length - 1; i >= 0; i--) + sb.EmitPush(args[i]); + + invocationScript = sb.ToArray(); + tx.Witnesses ??= new Witness[] { new() { InvocationScript = invocationScript } }; + engine.LoadScript(new Script(invocationScript), configureState: p => p.CallFlags = CallFlags.None); + } + JObject json = new(); + json["script"] = Convert.ToBase64String(invocationScript); + json["state"] = engine.Execute(); + // Gas consumed in the unit of datoshi, 1 GAS = 1e8 datoshi + json["gasconsumed"] = engine.FeeConsumed.ToString(); + json["exception"] = GetExceptionMessage(engine.FaultException); + try + { + json["stack"] = new JArray(engine.ResultStack.Select(p => p.ToJson(settings.MaxStackSize))); + } + catch (Exception ex) + { + json["exception"] = ex.Message; + } + return json; + } + + private JObject SignAndRelay(DataCache snapshot, Transaction tx) + { + ContractParametersContext context = new(snapshot, tx, settings.Network); + wallet.Sign(context); + if (context.Completed) + { + tx.Witnesses = context.GetWitnesses(); + system.Blockchain.Tell(tx); + return Utility.TransactionToJson(tx, system.Settings); + } + else + { + return context.ToJson(); + } + } + + internal static UInt160 AddressToScriptHash(string address, byte version) + { + if (UInt160.TryParse(address, out var scriptHash)) + { + return scriptHash; + } + + return address.ToScriptHash(version); + } + } +} diff --git a/src/Plugins/RpcServer/RpcServer.cs b/src/Plugins/RpcServer/RpcServer.cs new file mode 100644 index 0000000000..c53f17b860 --- /dev/null +++ b/src/Plugins/RpcServer/RpcServer.cs @@ -0,0 +1,312 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcServer.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Akka.Actor; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.ResponseCompression; +using Microsoft.AspNetCore.Server.Kestrel.Https; +using Microsoft.Extensions.DependencyInjection; +using Neo.Json; +using Neo.Network.P2P; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Net.Security; +using System.Reflection; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.Plugins.RpcServer +{ + public partial class RpcServer : IDisposable + { + private const int MaxParamsDepth = 32; + + private readonly Dictionary> methods = new(); + + private IWebHost host; + private RpcServerSettings settings; + private readonly NeoSystem system; + private readonly LocalNode localNode; + + public RpcServer(NeoSystem system, RpcServerSettings settings) + { + this.system = system; + this.settings = settings; + localNode = system.LocalNode.Ask(new LocalNode.GetInstance()).Result; + RegisterMethods(this); + Initialize_SmartContract(); + } + + internal bool CheckAuth(HttpContext context) + { + if (string.IsNullOrEmpty(settings.RpcUser)) return true; + + string reqauth = context.Request.Headers["Authorization"]; + if (string.IsNullOrEmpty(reqauth)) + { + context.Response.Headers["WWW-Authenticate"] = "Basic realm=\"Restricted\""; + context.Response.StatusCode = 401; + return false; + } + + string authstring; + try + { + authstring = Encoding.UTF8.GetString(Convert.FromBase64String(reqauth.Replace("Basic ", "").Trim())); + } + catch + { + return false; + } + + string[] authvalues = authstring.Split(new string[] { ":" }, StringSplitOptions.RemoveEmptyEntries); + if (authvalues.Length < 2) + return false; + + return authvalues[0] == settings.RpcUser && authvalues[1] == settings.RpcPass; + } + + private static JObject CreateErrorResponse(JToken id, RpcError rpcError) + { + JObject response = CreateResponse(id); + response["error"] = rpcError.ToJson(); + return response; + } + + private static JObject CreateResponse(JToken id) + { + JObject response = new(); + response["jsonrpc"] = "2.0"; + response["id"] = id; + return response; + } + + public void Dispose() + { + Dispose_SmartContract(); + if (host != null) + { + host.Dispose(); + host = null; + } + } + + public void StartRpcServer() + { + host = new WebHostBuilder().UseKestrel(options => options.Listen(settings.BindAddress, settings.Port, listenOptions => + { + // Default value is 5Mb + options.Limits.MaxRequestBodySize = settings.MaxRequestBodySize; + options.Limits.MaxRequestLineSize = Math.Min(settings.MaxRequestBodySize, options.Limits.MaxRequestLineSize); + // Default value is 40 + options.Limits.MaxConcurrentConnections = settings.MaxConcurrentConnections; + + // Default value is 1 minutes + options.Limits.KeepAliveTimeout = settings.KeepAliveTimeout == -1 ? + TimeSpan.MaxValue : + TimeSpan.FromSeconds(settings.KeepAliveTimeout); + + // Default value is 15 seconds + options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(settings.RequestHeadersTimeout); + + if (string.IsNullOrEmpty(settings.SslCert)) return; + listenOptions.UseHttps(settings.SslCert, settings.SslCertPassword, httpsConnectionAdapterOptions => + { + if (settings.TrustedAuthorities is null || settings.TrustedAuthorities.Length == 0) + return; + httpsConnectionAdapterOptions.ClientCertificateMode = ClientCertificateMode.RequireCertificate; + httpsConnectionAdapterOptions.ClientCertificateValidation = (cert, chain, err) => + { + if (err != SslPolicyErrors.None) + return false; + X509Certificate2 authority = chain.ChainElements[^1].Certificate; + return settings.TrustedAuthorities.Contains(authority.Thumbprint); + }; + }); + })) + .Configure(app => + { + if (settings.EnableCors) + app.UseCors("All"); + + app.UseResponseCompression(); + app.Run(ProcessAsync); + }) + .ConfigureServices(services => + { + if (settings.EnableCors) + { + if (settings.AllowOrigins.Length == 0) + services.AddCors(options => + { + options.AddPolicy("All", policy => + { + policy.AllowAnyOrigin() + .WithHeaders("Content-Type") + .WithMethods("GET", "POST"); + // The CORS specification states that setting origins to "*" (all origins) + // is invalid if the Access-Control-Allow-Credentials header is present. + }); + }); + else + services.AddCors(options => + { + options.AddPolicy("All", policy => + { + policy.WithOrigins(settings.AllowOrigins) + .WithHeaders("Content-Type") + .AllowCredentials() + .WithMethods("GET", "POST"); + }); + }); + } + + services.AddResponseCompression(options => + { + // options.EnableForHttps = false; + options.Providers.Add(); + options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Append("application/json"); + }); + + services.Configure(options => + { + options.Level = CompressionLevel.Fastest; + }); + }) + .Build(); + + host.Start(); + } + + internal void UpdateSettings(RpcServerSettings settings) + { + this.settings = settings; + } + + public async Task ProcessAsync(HttpContext context) + { + if (context.Request.Method != "GET" && context.Request.Method != "POST") return; + JToken request = null; + if (context.Request.Method == "GET") + { + string jsonrpc = context.Request.Query["jsonrpc"]; + string id = context.Request.Query["id"]; + string method = context.Request.Query["method"]; + string _params = context.Request.Query["params"]; + if (!string.IsNullOrEmpty(id) && !string.IsNullOrEmpty(method) && !string.IsNullOrEmpty(_params)) + { + try + { + _params = Encoding.UTF8.GetString(Convert.FromBase64String(_params)); + } + catch (FormatException) { } + request = new JObject(); + if (!string.IsNullOrEmpty(jsonrpc)) + request["jsonrpc"] = jsonrpc; + request["id"] = id; + request["method"] = method; + request["params"] = JToken.Parse(_params, MaxParamsDepth); + } + } + else if (context.Request.Method == "POST") + { + using StreamReader reader = new(context.Request.Body); + try + { + request = JToken.Parse(await reader.ReadToEndAsync(), MaxParamsDepth); + } + catch (FormatException) { } + } + JToken response; + if (request == null) + { + response = CreateErrorResponse(null, RpcError.BadRequest); + } + else if (request is JArray array) + { + if (array.Count == 0) + { + response = CreateErrorResponse(request["id"], RpcError.InvalidRequest); + } + else + { + var tasks = array.Select(p => ProcessRequestAsync(context, (JObject)p)); + var results = await Task.WhenAll(tasks); + response = results.Where(p => p != null).ToArray(); + } + } + else + { + response = await ProcessRequestAsync(context, (JObject)request); + } + if (response == null || (response as JArray)?.Count == 0) return; + context.Response.ContentType = "application/json"; + await context.Response.WriteAsync(response.ToString(), Encoding.UTF8); + } + + private async Task ProcessRequestAsync(HttpContext context, JObject request) + { + if (!request.ContainsProperty("id")) return null; + JToken @params = request["params"] ?? new JArray(); + if (!request.ContainsProperty("method") || @params is not JArray) + { + return CreateErrorResponse(request["id"], RpcError.InvalidRequest); + } + JObject response = CreateResponse(request["id"]); + try + { + string method = request["method"].AsString(); + (CheckAuth(context) && !settings.DisabledMethods.Contains(method)).True_Or(RpcError.AccessDenied); + methods.TryGetValue(method, out var func).True_Or(RpcErrorFactory.MethodNotFound(method)); + response["result"] = func((JArray)@params) switch + { + JToken result => result, + Task task => await task, + _ => throw new NotSupportedException() + }; + return response; + } + catch (FormatException ex) + { + return CreateErrorResponse(request["id"], RpcError.InvalidParams.WithData(ex.Message)); + } + catch (IndexOutOfRangeException ex) + { + return CreateErrorResponse(request["id"], RpcError.InvalidParams.WithData(ex.Message)); + } + catch (Exception ex) + { +#if DEBUG + return CreateErrorResponse(request["id"], RpcErrorFactory.NewCustomError(ex.HResult, ex.Message, ex.StackTrace)); +#else + return CreateErrorResponse(request["id"], RpcErrorFactory.NewCustomError(ex.HResult, ex.Message)); +#endif + } + } + + public void RegisterMethods(object handler) + { + foreach (MethodInfo method in handler.GetType().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) + { + RpcMethodAttribute attribute = method.GetCustomAttribute(); + if (attribute is null) continue; + string name = string.IsNullOrEmpty(attribute.Name) ? method.Name.ToLowerInvariant() : attribute.Name; + methods[name] = method.CreateDelegate>(handler); + } + } + } +} diff --git a/src/Plugins/RpcServer/RpcServer.csproj b/src/Plugins/RpcServer/RpcServer.csproj new file mode 100644 index 0000000000..c5b72e7a61 --- /dev/null +++ b/src/Plugins/RpcServer/RpcServer.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + Neo.Plugins.RpcServer + ../../../bin/$(PackageId) + + + + + + + + + PreserveNewest + + + + + + + + diff --git a/src/Plugins/RpcServer/RpcServer.json b/src/Plugins/RpcServer/RpcServer.json new file mode 100644 index 0000000000..8f6905dead --- /dev/null +++ b/src/Plugins/RpcServer/RpcServer.json @@ -0,0 +1,29 @@ +{ + "PluginConfiguration": { + "Servers": [ + { + "Network": 860833102, + "BindAddress": "127.0.0.1", + "Port": 10332, + "SslCert": "", + "SslCertPassword": "", + "TrustedAuthorities": [], + "RpcUser": "", + "RpcPass": "", + "EnableCors": true, + "AllowOrigins": [], + "KeepAliveTimeout": 60, + "RequestHeadersTimeout": 15, + "MaxGasInvoke": 20, + "MaxFee": 0.1, + "MaxConcurrentConnections": 40, + "MaxIteratorResultItems": 100, + "MaxStackSize": 65535, + "DisabledMethods": [ "openwallet" ], + "SessionEnabled": false, + "SessionExpirationTime": 60, + "FindStoragePageSize": 50 + } + ] + } +} diff --git a/src/Plugins/RpcServer/RpcServerPlugin.cs b/src/Plugins/RpcServer/RpcServerPlugin.cs new file mode 100644 index 0000000000..c22462d139 --- /dev/null +++ b/src/Plugins/RpcServer/RpcServerPlugin.cs @@ -0,0 +1,87 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcServerPlugin.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Collections.Generic; +using System.Linq; + +namespace Neo.Plugins.RpcServer +{ + public class RpcServerPlugin : Plugin + { + public override string Name => "RpcServer"; + public override string Description => "Enables RPC for the node"; + + private Settings settings; + private static readonly Dictionary servers = new(); + private static readonly Dictionary> handlers = new(); + + public override string ConfigFile => System.IO.Path.Combine(RootPath, "RpcServer.json"); + + protected override void Configure() + { + settings = new Settings(GetConfiguration()); + foreach (RpcServerSettings s in settings.Servers) + if (servers.TryGetValue(s.Network, out RpcServer server)) + server.UpdateSettings(s); + } + + public override void Dispose() + { + foreach (var (_, server) in servers) + server.Dispose(); + base.Dispose(); + } + + protected override void OnSystemLoaded(NeoSystem system) + { + RpcServerSettings s = settings.Servers.FirstOrDefault(p => p.Network == system.Settings.Network); + if (s is null) return; + + if (s.EnableCors && string.IsNullOrEmpty(s.RpcUser) == false && s.AllowOrigins.Length == 0) + { + Log("RcpServer: CORS is misconfigured!", LogLevel.Warning); + Log($"You have {nameof(s.EnableCors)} and Basic Authentication enabled but " + + $"{nameof(s.AllowOrigins)} is empty in config.json for RcpServer. " + + "You must add url origins to the list to have CORS work from " + + $"browser with basic authentication enabled. " + + $"Example: \"AllowOrigins\": [\"http://{s.BindAddress}:{s.Port}\"]", LogLevel.Info); + } + + RpcServer rpcRpcServer = new(system, s); + + if (handlers.Remove(s.Network, out var list)) + { + foreach (var handler in list) + { + rpcRpcServer.RegisterMethods(handler); + } + } + + rpcRpcServer.StartRpcServer(); + servers.TryAdd(s.Network, rpcRpcServer); + } + + public static void RegisterMethods(object handler, uint network) + { + if (servers.TryGetValue(network, out RpcServer server)) + { + server.RegisterMethods(handler); + return; + } + if (!handlers.TryGetValue(network, out var list)) + { + list = new List(); + handlers.Add(network, list); + } + list.Add(handler); + } + } +} diff --git a/src/Plugins/RpcServer/Session.cs b/src/Plugins/RpcServer/Session.cs new file mode 100644 index 0000000000..1dd8808dde --- /dev/null +++ b/src/Plugins/RpcServer/Session.cs @@ -0,0 +1,58 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Session.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Iterators; +using Neo.SmartContract.Native; +using System; +using System.Collections.Generic; + +namespace Neo.Plugins.RpcServer +{ + class Session : IDisposable + { + public readonly SnapshotCache Snapshot; + public readonly ApplicationEngine Engine; + public readonly Dictionary Iterators = new(); + public DateTime StartTime; + + public Session(NeoSystem system, byte[] script, Signer[] signers, Witness[] witnesses, long datoshi, Diagnostic diagnostic) + { + Random random = new(); + Snapshot = system.GetSnapshot(); + Transaction tx = signers == null ? null : new Transaction + { + Version = 0, + Nonce = (uint)random.Next(), + ValidUntilBlock = NativeContract.Ledger.CurrentIndex(Snapshot) + system.Settings.MaxValidUntilBlockIncrement, + Signers = signers, + Attributes = Array.Empty(), + Script = script, + Witnesses = witnesses + }; + Engine = ApplicationEngine.Run(script, Snapshot, container: tx, settings: system.Settings, gas: datoshi, diagnostic: diagnostic); + ResetExpiration(); + } + + public void ResetExpiration() + { + StartTime = DateTime.UtcNow; + } + + public void Dispose() + { + Engine.Dispose(); + Snapshot.Dispose(); + } + } +} diff --git a/src/Plugins/RpcServer/Settings.cs b/src/Plugins/RpcServer/Settings.cs new file mode 100644 index 0000000000..ad624d9082 --- /dev/null +++ b/src/Plugins/RpcServer/Settings.cs @@ -0,0 +1,107 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Settings.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.Extensions.Configuration; +using Neo.SmartContract.Native; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; + +namespace Neo.Plugins.RpcServer +{ + class Settings + { + public IReadOnlyList Servers { get; init; } + + public Settings(IConfigurationSection section) + { + Servers = section.GetSection(nameof(Servers)).GetChildren().Select(p => RpcServerSettings.Load(p)).ToArray(); + } + } + + public record RpcServerSettings + { + public uint Network { get; init; } + public IPAddress BindAddress { get; init; } + public ushort Port { get; init; } + public string SslCert { get; init; } + public string SslCertPassword { get; init; } + public string[] TrustedAuthorities { get; init; } + public int MaxConcurrentConnections { get; init; } + public int MaxRequestBodySize { get; init; } + public string RpcUser { get; init; } + public string RpcPass { get; init; } + public bool EnableCors { get; init; } + public string[] AllowOrigins { get; init; } + public int KeepAliveTimeout { get; init; } + public uint RequestHeadersTimeout { get; init; } + // In the unit of datoshi, 1 GAS = 10^8 datoshi + public long MaxGasInvoke { get; init; } + // In the unit of datoshi, 1 GAS = 10^8 datoshi + public long MaxFee { get; init; } + public int MaxIteratorResultItems { get; init; } + public int MaxStackSize { get; init; } + public string[] DisabledMethods { get; init; } + public bool SessionEnabled { get; init; } + public TimeSpan SessionExpirationTime { get; init; } + public int FindStoragePageSize { get; init; } + + public static RpcServerSettings Default { get; } = new RpcServerSettings + { + Network = 5195086u, + BindAddress = IPAddress.None, + SslCert = string.Empty, + SslCertPassword = string.Empty, + MaxGasInvoke = (long)new BigDecimal(10M, NativeContract.GAS.Decimals).Value, + MaxFee = (long)new BigDecimal(0.1M, NativeContract.GAS.Decimals).Value, + TrustedAuthorities = Array.Empty(), + EnableCors = true, + AllowOrigins = Array.Empty(), + KeepAliveTimeout = 60, + RequestHeadersTimeout = 15, + MaxIteratorResultItems = 100, + MaxStackSize = ushort.MaxValue, + DisabledMethods = Array.Empty(), + MaxConcurrentConnections = 40, + MaxRequestBodySize = 5 * 1024 * 1024, + SessionEnabled = false, + SessionExpirationTime = TimeSpan.FromSeconds(60), + FindStoragePageSize = 50 + }; + + public static RpcServerSettings Load(IConfigurationSection section) => new() + { + Network = section.GetValue("Network", Default.Network), + BindAddress = IPAddress.Parse(section.GetSection("BindAddress").Value), + Port = ushort.Parse(section.GetSection("Port").Value), + SslCert = section.GetSection("SslCert").Value, + SslCertPassword = section.GetSection("SslCertPassword").Value, + TrustedAuthorities = section.GetSection("TrustedAuthorities").GetChildren().Select(p => p.Get()).ToArray(), + RpcUser = section.GetSection("RpcUser").Value, + RpcPass = section.GetSection("RpcPass").Value, + EnableCors = section.GetValue(nameof(EnableCors), Default.EnableCors), + AllowOrigins = section.GetSection(nameof(AllowOrigins)).GetChildren().Select(p => p.Get()).ToArray(), + KeepAliveTimeout = section.GetValue(nameof(KeepAliveTimeout), Default.KeepAliveTimeout), + RequestHeadersTimeout = section.GetValue(nameof(RequestHeadersTimeout), Default.RequestHeadersTimeout), + MaxGasInvoke = (long)new BigDecimal(section.GetValue("MaxGasInvoke", Default.MaxGasInvoke), NativeContract.GAS.Decimals).Value, + MaxFee = (long)new BigDecimal(section.GetValue("MaxFee", Default.MaxFee), NativeContract.GAS.Decimals).Value, + MaxIteratorResultItems = section.GetValue("MaxIteratorResultItems", Default.MaxIteratorResultItems), + MaxStackSize = section.GetValue("MaxStackSize", Default.MaxStackSize), + DisabledMethods = section.GetSection("DisabledMethods").GetChildren().Select(p => p.Get()).ToArray(), + MaxConcurrentConnections = section.GetValue("MaxConcurrentConnections", Default.MaxConcurrentConnections), + MaxRequestBodySize = section.GetValue("MaxRequestBodySize", Default.MaxRequestBodySize), + SessionEnabled = section.GetValue("SessionEnabled", Default.SessionEnabled), + SessionExpirationTime = TimeSpan.FromSeconds(section.GetValue("SessionExpirationTime", (int)Default.SessionExpirationTime.TotalSeconds)), + FindStoragePageSize = section.GetValue("FindStoragePageSize", Default.FindStoragePageSize) + }; + } +} diff --git a/src/Plugins/RpcServer/Tree.cs b/src/Plugins/RpcServer/Tree.cs new file mode 100644 index 0000000000..b2d1afc11a --- /dev/null +++ b/src/Plugins/RpcServer/Tree.cs @@ -0,0 +1,36 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Tree.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Collections.Generic; + +namespace Neo.Plugins.RpcServer +{ + class Tree + { + public TreeNode Root { get; private set; } + + public TreeNode AddRoot(T item) + { + if (Root is not null) + throw new InvalidOperationException(); + Root = new TreeNode(item, null); + return Root; + } + + public IEnumerable GetItems() + { + if (Root is null) yield break; + foreach (T item in Root.GetItems()) + yield return item; + } + } +} diff --git a/src/Plugins/RpcServer/TreeNode.cs b/src/Plugins/RpcServer/TreeNode.cs new file mode 100644 index 0000000000..0ba06a689f --- /dev/null +++ b/src/Plugins/RpcServer/TreeNode.cs @@ -0,0 +1,45 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TreeNode.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Collections.Generic; + +namespace Neo.Plugins.RpcServer +{ + class TreeNode + { + private readonly List> children = new(); + + public T Item { get; } + public TreeNode Parent { get; } + public IReadOnlyList> Children => children; + + internal TreeNode(T item, TreeNode parent) + { + Item = item; + Parent = parent; + } + + public TreeNode AddChild(T item) + { + TreeNode child = new(item, this); + children.Add(child); + return child; + } + + internal IEnumerable GetItems() + { + yield return Item; + foreach (var child in children) + foreach (T item in child.GetItems()) + yield return item; + } + } +} diff --git a/src/Plugins/RpcServer/Utility.cs b/src/Plugins/RpcServer/Utility.cs new file mode 100644 index 0000000000..1dda8c131c --- /dev/null +++ b/src/Plugins/RpcServer/Utility.cs @@ -0,0 +1,36 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Utility.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using Neo.Network.P2P.Payloads; +using Neo.SmartContract.Native; +using System.Linq; + +namespace Neo.Plugins.RpcServer +{ + static class Utility + { + public static JObject BlockToJson(Block block, ProtocolSettings settings) + { + JObject json = block.ToJson(settings); + json["tx"] = block.Transactions.Select(p => TransactionToJson(p, settings)).ToArray(); + return json; + } + + public static JObject TransactionToJson(Transaction tx, ProtocolSettings settings) + { + JObject json = tx.ToJson(settings); + json["sysfee"] = tx.SystemFee.ToString(); + json["netfee"] = tx.NetworkFee.ToString(); + return json; + } + } +} diff --git a/src/Plugins/SQLiteWallet/Account.cs b/src/Plugins/SQLiteWallet/Account.cs new file mode 100644 index 0000000000..e7ae09af90 --- /dev/null +++ b/src/Plugins/SQLiteWallet/Account.cs @@ -0,0 +1,18 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Account.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.Wallets.SQLite; + +class Account +{ + public byte[] PublicKeyHash { get; set; } + public string Nep2key { get; set; } +} diff --git a/src/Plugins/SQLiteWallet/Address.cs b/src/Plugins/SQLiteWallet/Address.cs new file mode 100644 index 0000000000..6f2b73e427 --- /dev/null +++ b/src/Plugins/SQLiteWallet/Address.cs @@ -0,0 +1,17 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Address.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.Wallets.SQLite; + +class Address +{ + public byte[] ScriptHash { get; set; } +} diff --git a/src/Plugins/SQLiteWallet/Contract.cs b/src/Plugins/SQLiteWallet/Contract.cs new file mode 100644 index 0000000000..2da432a63b --- /dev/null +++ b/src/Plugins/SQLiteWallet/Contract.cs @@ -0,0 +1,21 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Contract.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.Wallets.SQLite; + +class Contract +{ + public byte[] RawData { get; set; } + public byte[] ScriptHash { get; set; } + public byte[] PublicKeyHash { get; set; } + public Account Account { get; set; } + public Address Address { get; set; } +} diff --git a/src/Plugins/SQLiteWallet/Key.cs b/src/Plugins/SQLiteWallet/Key.cs new file mode 100644 index 0000000000..81a8a6daf4 --- /dev/null +++ b/src/Plugins/SQLiteWallet/Key.cs @@ -0,0 +1,18 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Key.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.Wallets.SQLite; + +class Key +{ + public string Name { get; set; } + public byte[] Value { get; set; } +} diff --git a/src/Plugins/SQLiteWallet/SQLiteWallet.cs b/src/Plugins/SQLiteWallet/SQLiteWallet.cs new file mode 100644 index 0000000000..a4004dcff0 --- /dev/null +++ b/src/Plugins/SQLiteWallet/SQLiteWallet.cs @@ -0,0 +1,416 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// SQLiteWallet.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.EntityFrameworkCore; +using Neo.Cryptography; +using Neo.IO; +using Neo.SmartContract; +using Neo.Wallets.NEP6; +using System.Buffers.Binary; +using System.Reflection; +using System.Security.Cryptography; +using System.Text; +using static System.IO.Path; + +namespace Neo.Wallets.SQLite; + +/// +/// A wallet implementation that uses SQLite as the underlying storage. +/// +class SQLiteWallet : Wallet +{ + private readonly object db_lock = new(); + private readonly byte[] iv; + private readonly byte[] salt; + private readonly byte[] masterKey; + private readonly ScryptParameters scrypt; + private readonly Dictionary accounts; + + public override string Name => GetFileNameWithoutExtension(Path); + + public override Version Version + { + get + { + byte[] buffer = LoadStoredData("Version"); + if (buffer == null || buffer.Length < 16) return new Version(0, 0); + int major = BinaryPrimitives.ReadInt32LittleEndian(buffer); + int minor = BinaryPrimitives.ReadInt32LittleEndian(buffer.AsSpan(4)); + int build = BinaryPrimitives.ReadInt32LittleEndian(buffer.AsSpan(8)); + int revision = BinaryPrimitives.ReadInt32LittleEndian(buffer.AsSpan(12)); + return new Version(major, minor, build, revision); + } + } + + private SQLiteWallet(string path, byte[] passwordKey, ProtocolSettings settings) : base(path, settings) + { + salt = LoadStoredData("Salt"); + byte[] passwordHash = LoadStoredData("PasswordHash"); + if (passwordHash != null && !passwordHash.SequenceEqual(passwordKey.Concat(salt).ToArray().Sha256())) + throw new CryptographicException(); + iv = LoadStoredData("IV"); + masterKey = Decrypt(LoadStoredData("MasterKey"), passwordKey, iv); + scrypt = new ScryptParameters + ( + BinaryPrimitives.ReadInt32LittleEndian(LoadStoredData("ScryptN")), + BinaryPrimitives.ReadInt32LittleEndian(LoadStoredData("ScryptR")), + BinaryPrimitives.ReadInt32LittleEndian(LoadStoredData("ScryptP")) + ); + accounts = LoadAccounts(); + } + + private SQLiteWallet(string path, byte[] passwordKey, ProtocolSettings settings, ScryptParameters scrypt) : base(path, settings) + { + iv = new byte[16]; + salt = new byte[20]; + masterKey = new byte[32]; + this.scrypt = scrypt; + accounts = new Dictionary(); + using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(iv); + rng.GetBytes(salt); + rng.GetBytes(masterKey); + } + Version version = Assembly.GetExecutingAssembly().GetName().Version; + byte[] versionBuffer = new byte[sizeof(int) * 4]; + BinaryPrimitives.WriteInt32LittleEndian(versionBuffer, version.Major); + BinaryPrimitives.WriteInt32LittleEndian(versionBuffer.AsSpan(4), version.Minor); + BinaryPrimitives.WriteInt32LittleEndian(versionBuffer.AsSpan(8), version.Build); + BinaryPrimitives.WriteInt32LittleEndian(versionBuffer.AsSpan(12), version.Revision); + BuildDatabase(); + SaveStoredData("IV", iv); + SaveStoredData("Salt", salt); + SaveStoredData("PasswordHash", passwordKey.Concat(salt).ToArray().Sha256()); + SaveStoredData("MasterKey", Encrypt(masterKey, passwordKey, iv)); + SaveStoredData("Version", versionBuffer); + SaveStoredData("ScryptN", this.scrypt.N); + SaveStoredData("ScryptR", this.scrypt.R); + SaveStoredData("ScryptP", this.scrypt.P); + } + + private void AddAccount(SQLiteWalletAccount account) + { + lock (accounts) + { + if (accounts.TryGetValue(account.ScriptHash, out SQLiteWalletAccount account_old)) + { + if (account.Contract == null) + { + account.Contract = account_old.Contract; + } + } + accounts[account.ScriptHash] = account; + } + lock (db_lock) + { + using WalletDataContext ctx = new(Path); + if (account.HasKey) + { + Account db_account = ctx.Accounts.FirstOrDefault(p => p.PublicKeyHash == account.Key.PublicKeyHash.ToArray()); + if (db_account == null) + { + db_account = ctx.Accounts.Add(new Account + { + Nep2key = account.Key.Export(masterKey, ProtocolSettings.AddressVersion, scrypt.N, scrypt.R, scrypt.P), + PublicKeyHash = account.Key.PublicKeyHash.ToArray() + }).Entity; + } + else + { + db_account.Nep2key = account.Key.Export(masterKey, ProtocolSettings.AddressVersion, scrypt.N, scrypt.R, scrypt.P); + } + } + if (account.Contract != null) + { + Contract db_contract = ctx.Contracts.FirstOrDefault(p => p.ScriptHash == account.Contract.ScriptHash.ToArray()); + if (db_contract != null) + { + db_contract.PublicKeyHash = account.Key.PublicKeyHash.ToArray(); + } + else + { + ctx.Contracts.Add(new Contract + { + RawData = ((VerificationContract)account.Contract).ToArray(), + ScriptHash = account.Contract.ScriptHash.ToArray(), + PublicKeyHash = account.Key.PublicKeyHash.ToArray() + }); + } + } + //add address + { + Address db_address = ctx.Addresses.FirstOrDefault(p => p.ScriptHash == account.ScriptHash.ToArray()); + if (db_address == null) + { + ctx.Addresses.Add(new Address + { + ScriptHash = account.ScriptHash.ToArray() + }); + } + } + ctx.SaveChanges(); + } + } + + private void BuildDatabase() + { + using WalletDataContext ctx = new(Path); + ctx.Database.EnsureDeleted(); + ctx.Database.EnsureCreated(); + } + + public override bool ChangePassword(string oldPassword, string newPassword) + { + if (!VerifyPassword(oldPassword)) return false; + byte[] passwordKey = ToAesKey(newPassword); + try + { + SaveStoredData("PasswordHash", passwordKey.Concat(salt).ToArray().Sha256()); + SaveStoredData("MasterKey", Encrypt(masterKey, passwordKey, iv)); + return true; + } + finally + { + Array.Clear(passwordKey, 0, passwordKey.Length); + } + } + + public override bool Contains(UInt160 scriptHash) + { + lock (accounts) + { + return accounts.ContainsKey(scriptHash); + } + } + + /// + /// Creates a new wallet at the specified path. + /// + /// The path of the wallet. + /// The password of the wallet. + /// The to be used by the wallet. + /// The parameters of the SCrypt algorithm used for encrypting and decrypting the private keys in the wallet. + /// The created wallet. + public static SQLiteWallet Create(string path, string password, ProtocolSettings settings, ScryptParameters scrypt = null) + { + return new SQLiteWallet(path, ToAesKey(password), settings, scrypt ?? ScryptParameters.Default); + } + + public override WalletAccount CreateAccount(byte[] privateKey) + { + KeyPair key = new(privateKey); + VerificationContract contract = new() + { + Script = SmartContract.Contract.CreateSignatureRedeemScript(key.PublicKey), + ParameterList = new[] { ContractParameterType.Signature } + }; + SQLiteWalletAccount account = new(contract.ScriptHash, ProtocolSettings) + { + Key = key, + Contract = contract + }; + AddAccount(account); + return account; + } + + public override WalletAccount CreateAccount(SmartContract.Contract contract, KeyPair key = null) + { + if (contract is not VerificationContract verification_contract) + { + verification_contract = new VerificationContract + { + Script = contract.Script, + ParameterList = contract.ParameterList + }; + } + SQLiteWalletAccount account = new(verification_contract.ScriptHash, ProtocolSettings) + { + Key = key, + Contract = verification_contract + }; + AddAccount(account); + return account; + } + + public override WalletAccount CreateAccount(UInt160 scriptHash) + { + SQLiteWalletAccount account = new(scriptHash, ProtocolSettings); + AddAccount(account); + return account; + } + + public override void Delete() + { + using WalletDataContext ctx = new(Path); + ctx.Database.EnsureDeleted(); + } + + public override bool DeleteAccount(UInt160 scriptHash) + { + SQLiteWalletAccount account; + lock (accounts) + { + if (accounts.TryGetValue(scriptHash, out account)) + accounts.Remove(scriptHash); + } + if (account != null) + { + lock (db_lock) + { + using WalletDataContext ctx = new(Path); + if (account.HasKey) + { + Account db_account = ctx.Accounts.First(p => p.PublicKeyHash == account.Key.PublicKeyHash.ToArray()); + ctx.Accounts.Remove(db_account); + } + if (account.Contract != null) + { + Contract db_contract = ctx.Contracts.First(p => p.ScriptHash == scriptHash.ToArray()); + ctx.Contracts.Remove(db_contract); + } + //delete address + { + Address db_address = ctx.Addresses.First(p => p.ScriptHash == scriptHash.ToArray()); + ctx.Addresses.Remove(db_address); + } + ctx.SaveChanges(); + } + return true; + } + return false; + } + + public override WalletAccount GetAccount(UInt160 scriptHash) + { + lock (accounts) + { + accounts.TryGetValue(scriptHash, out SQLiteWalletAccount account); + return account; + } + } + + public override IEnumerable GetAccounts() + { + lock (accounts) + { + foreach (SQLiteWalletAccount account in accounts.Values) + yield return account; + } + } + + private Dictionary LoadAccounts() + { + using WalletDataContext ctx = new(Path); + Dictionary accounts = ctx.Addresses.Select(p => p.ScriptHash).AsEnumerable().Select(p => new SQLiteWalletAccount(new UInt160(p), ProtocolSettings)).ToDictionary(p => p.ScriptHash); + foreach (Contract db_contract in ctx.Contracts.Include(p => p.Account)) + { + VerificationContract contract = db_contract.RawData.AsSerializable(); + SQLiteWalletAccount account = accounts[contract.ScriptHash]; + account.Contract = contract; + account.Key = new KeyPair(GetPrivateKeyFromNEP2(db_contract.Account.Nep2key, masterKey, ProtocolSettings.AddressVersion, scrypt.N, scrypt.R, scrypt.P)); + } + return accounts; + } + + private byte[] LoadStoredData(string name) + { + using WalletDataContext ctx = new(Path); + return ctx.Keys.FirstOrDefault(p => p.Name == name)?.Value; + } + + /// + /// Opens a wallet at the specified path. + /// + /// The path of the wallet. + /// The password of the wallet. + /// The to be used by the wallet. + /// The opened wallet. + public static new SQLiteWallet Open(string path, string password, ProtocolSettings settings) + { + return new SQLiteWallet(path, ToAesKey(password), settings); + } + + public override void Save() + { + // Do nothing + } + + private void SaveStoredData(string name, int value) + { + byte[] data = new byte[sizeof(int)]; + BinaryPrimitives.WriteInt32LittleEndian(data, value); + SaveStoredData(name, data); + } + + private void SaveStoredData(string name, byte[] value) + { + lock (db_lock) + { + using WalletDataContext ctx = new(Path); + SaveStoredData(ctx, name, value); + ctx.SaveChanges(); + } + } + + private static void SaveStoredData(WalletDataContext ctx, string name, byte[] value) + { + Key key = ctx.Keys.FirstOrDefault(p => p.Name == name); + if (key == null) + { + ctx.Keys.Add(new Key + { + Name = name, + Value = value + }); + } + else + { + key.Value = value; + } + } + + public override bool VerifyPassword(string password) + { + return ToAesKey(password).Concat(salt).ToArray().Sha256().SequenceEqual(LoadStoredData("PasswordHash")); + } + + private static byte[] Encrypt(byte[] data, byte[] key, byte[] iv) + { + if (data == null || key == null || iv == null) throw new ArgumentNullException(); + if (data.Length % 16 != 0 || key.Length != 32 || iv.Length != 16) throw new ArgumentException(); + using Aes aes = Aes.Create(); + aes.Padding = PaddingMode.None; + using ICryptoTransform encryptor = aes.CreateEncryptor(key, iv); + return encryptor.TransformFinalBlock(data, 0, data.Length); + } + + private static byte[] Decrypt(byte[] data, byte[] key, byte[] iv) + { + if (data == null || key == null || iv == null) throw new ArgumentNullException(); + if (data.Length % 16 != 0 || key.Length != 32 || iv.Length != 16) throw new ArgumentException(); + using Aes aes = Aes.Create(); + aes.Padding = PaddingMode.None; + using ICryptoTransform decryptor = aes.CreateDecryptor(key, iv); + return decryptor.TransformFinalBlock(data, 0, data.Length); + } + + private static byte[] ToAesKey(string password) + { + using SHA256 sha256 = SHA256.Create(); + byte[] passwordBytes = Encoding.UTF8.GetBytes(password); + byte[] passwordHash = sha256.ComputeHash(passwordBytes); + byte[] passwordHash2 = sha256.ComputeHash(passwordHash); + Array.Clear(passwordBytes, 0, passwordBytes.Length); + Array.Clear(passwordHash, 0, passwordHash.Length); + return passwordHash2; + } +} diff --git a/src/Plugins/SQLiteWallet/SQLiteWallet.csproj b/src/Plugins/SQLiteWallet/SQLiteWallet.csproj new file mode 100644 index 0000000000..041233af79 --- /dev/null +++ b/src/Plugins/SQLiteWallet/SQLiteWallet.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + Neo.Wallets.SQLite + Neo.Wallets.SQLite + enable + ../../../bin/$(PackageId) + + + + + + + diff --git a/src/Plugins/SQLiteWallet/SQLiteWalletAccount.cs b/src/Plugins/SQLiteWallet/SQLiteWalletAccount.cs new file mode 100644 index 0000000000..88526f7e52 --- /dev/null +++ b/src/Plugins/SQLiteWallet/SQLiteWalletAccount.cs @@ -0,0 +1,29 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// SQLiteWalletAccount.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.Wallets.SQLite; + +sealed class SQLiteWalletAccount : WalletAccount +{ + public KeyPair Key; + + public override bool HasKey => Key != null; + + public SQLiteWalletAccount(UInt160 scriptHash, ProtocolSettings settings) + : base(scriptHash, settings) + { + } + + public override KeyPair GetKey() + { + return Key; + } +} diff --git a/src/Plugins/SQLiteWallet/SQLiteWalletFactory.cs b/src/Plugins/SQLiteWallet/SQLiteWalletFactory.cs new file mode 100644 index 0000000000..d952fd0fc3 --- /dev/null +++ b/src/Plugins/SQLiteWallet/SQLiteWalletFactory.cs @@ -0,0 +1,41 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// SQLiteWalletFactory.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Plugins; +using static System.IO.Path; + +namespace Neo.Wallets.SQLite; + +public class SQLiteWalletFactory : Plugin, IWalletFactory +{ + public override string Name => "SQLiteWallet"; + public override string Description => "A SQLite-based wallet provider that supports wallet files with .db3 suffix."; + + public SQLiteWalletFactory() + { + Wallet.RegisterFactory(this); + } + + public bool Handle(string path) + { + return GetExtension(path).ToLowerInvariant() == ".db3"; + } + + public Wallet CreateWallet(string name, string path, string password, ProtocolSettings settings) + { + return SQLiteWallet.Create(path, password, settings); + } + + public Wallet OpenWallet(string path, string password, ProtocolSettings settings) + { + return SQLiteWallet.Open(path, password, settings); + } +} diff --git a/src/Plugins/SQLiteWallet/VerificationContract.cs b/src/Plugins/SQLiteWallet/VerificationContract.cs new file mode 100644 index 0000000000..e128045adb --- /dev/null +++ b/src/Plugins/SQLiteWallet/VerificationContract.cs @@ -0,0 +1,56 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// VerificationContract.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using Neo.SmartContract; + +namespace Neo.Wallets.SQLite; + +class VerificationContract : SmartContract.Contract, IEquatable, ISerializable +{ + public int Size => ParameterList.GetVarSize() + Script.GetVarSize(); + + public void Deserialize(ref MemoryReader reader) + { + ReadOnlySpan span = reader.ReadVarMemory().Span; + ParameterList = new ContractParameterType[span.Length]; + for (int i = 0; i < span.Length; i++) + { + ParameterList[i] = (ContractParameterType)span[i]; + if (!Enum.IsDefined(typeof(ContractParameterType), ParameterList[i])) + throw new FormatException(); + } + Script = reader.ReadVarMemory().ToArray(); + } + + public bool Equals(VerificationContract other) + { + if (ReferenceEquals(this, other)) return true; + if (other is null) return false; + return ScriptHash.Equals(other.ScriptHash); + } + + public override bool Equals(object obj) + { + return Equals(obj as VerificationContract); + } + + public override int GetHashCode() + { + return ScriptHash.GetHashCode(); + } + + public void Serialize(BinaryWriter writer) + { + writer.WriteVarBytes(ParameterList.Select(p => (byte)p).ToArray()); + writer.WriteVarBytes(Script); + } +} diff --git a/src/Plugins/SQLiteWallet/WalletDataContext.cs b/src/Plugins/SQLiteWallet/WalletDataContext.cs new file mode 100644 index 0000000000..79ca538cd1 --- /dev/null +++ b/src/Plugins/SQLiteWallet/WalletDataContext.cs @@ -0,0 +1,64 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// WalletDataContext.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; + +namespace Neo.Wallets.SQLite; + +class WalletDataContext : DbContext +{ + public DbSet Accounts { get; set; } + public DbSet
Addresses { get; set; } + public DbSet Contracts { get; set; } + public DbSet Keys { get; set; } + + private readonly string filename; + + public WalletDataContext(string filename) + { + this.filename = filename; + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + base.OnConfiguring(optionsBuilder); + SqliteConnectionStringBuilder sb = new() + { + DataSource = filename + }; + optionsBuilder.UseSqlite(sb.ToString()); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + modelBuilder.Entity().ToTable(nameof(Account)); + modelBuilder.Entity().HasKey(p => p.PublicKeyHash); + modelBuilder.Entity().Property(p => p.Nep2key).HasColumnType("VarChar").HasMaxLength(byte.MaxValue).IsRequired(); + modelBuilder.Entity().Property(p => p.PublicKeyHash).HasColumnType("Binary").HasMaxLength(20).IsRequired(); + modelBuilder.Entity
().ToTable(nameof(Address)); + modelBuilder.Entity
().HasKey(p => p.ScriptHash); + modelBuilder.Entity
().Property(p => p.ScriptHash).HasColumnType("Binary").HasMaxLength(20).IsRequired(); + modelBuilder.Entity().ToTable(nameof(Contract)); + modelBuilder.Entity().HasKey(p => p.ScriptHash); + modelBuilder.Entity().HasIndex(p => p.PublicKeyHash); + modelBuilder.Entity().HasOne(p => p.Account).WithMany().HasForeignKey(p => p.PublicKeyHash).OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity().HasOne(p => p.Address).WithMany().HasForeignKey(p => p.ScriptHash).OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity().Property(p => p.RawData).HasColumnType("VarBinary").IsRequired(); + modelBuilder.Entity().Property(p => p.ScriptHash).HasColumnType("Binary").HasMaxLength(20).IsRequired(); + modelBuilder.Entity().Property(p => p.PublicKeyHash).HasColumnType("Binary").HasMaxLength(20).IsRequired(); + modelBuilder.Entity().ToTable(nameof(Key)); + modelBuilder.Entity().HasKey(p => p.Name); + modelBuilder.Entity().Property(p => p.Name).HasColumnType("VarChar").HasMaxLength(20).IsRequired(); + modelBuilder.Entity().Property(p => p.Value).HasColumnType("VarBinary").IsRequired(); + } +} diff --git a/src/Plugins/StateService/Network/MessageType.cs b/src/Plugins/StateService/Network/MessageType.cs new file mode 100644 index 0000000000..88ed5b2e9f --- /dev/null +++ b/src/Plugins/StateService/Network/MessageType.cs @@ -0,0 +1,19 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// MessageType.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.Plugins.StateService.Network +{ + enum MessageType : byte + { + Vote, + StateRoot, + } +} diff --git a/src/Plugins/StateService/Network/StateRoot.cs b/src/Plugins/StateService/Network/StateRoot.cs new file mode 100644 index 0000000000..5b4e8610f2 --- /dev/null +++ b/src/Plugins/StateService/Network/StateRoot.cs @@ -0,0 +1,123 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// StateRoot.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Cryptography.ECC; +using Neo.IO; +using Neo.Json; +using Neo.Network.P2P; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using System; +using System.IO; + +namespace Neo.Plugins.StateService.Network +{ + class StateRoot : IVerifiable + { + public const byte CurrentVersion = 0x00; + + public byte Version; + public uint Index; + public UInt256 RootHash; + public Witness Witness; + + private UInt256 _hash = null; + public UInt256 Hash + { + get + { + if (_hash is null) + { + _hash = this.CalculateHash(); + } + return _hash; + } + } + + Witness[] IVerifiable.Witnesses + { + get + { + return new[] { Witness }; + } + set + { + if (value.Length != 1) throw new ArgumentException(null, nameof(value)); + Witness = value[0]; + } + } + + int ISerializable.Size => + sizeof(byte) + //Version + sizeof(uint) + //Index + UInt256.Length + //RootHash + (Witness is null ? 1 : 1 + Witness.Size); //Witness + + void ISerializable.Deserialize(ref MemoryReader reader) + { + DeserializeUnsigned(ref reader); + Witness[] witnesses = reader.ReadSerializableArray(1); + Witness = witnesses.Length switch + { + 0 => null, + 1 => witnesses[0], + _ => throw new FormatException(), + }; + } + + public void DeserializeUnsigned(ref MemoryReader reader) + { + Version = reader.ReadByte(); + Index = reader.ReadUInt32(); + RootHash = reader.ReadSerializable(); + } + + void ISerializable.Serialize(BinaryWriter writer) + { + SerializeUnsigned(writer); + if (Witness is null) + writer.WriteVarInt(0); + else + writer.Write(new[] { Witness }); + } + + public void SerializeUnsigned(BinaryWriter writer) + { + writer.Write(Version); + writer.Write(Index); + writer.Write(RootHash); + } + + public bool Verify(ProtocolSettings settings, DataCache snapshot) + { + return this.VerifyWitnesses(settings, snapshot, 2_00000000L); + } + + public UInt160[] GetScriptHashesForVerifying(DataCache snapshot) + { + ECPoint[] validators = NativeContract.RoleManagement.GetDesignatedByRole(snapshot, Role.StateValidator, Index); + if (validators.Length < 1) throw new InvalidOperationException("No script hash for state root verifying"); + return new UInt160[] { Contract.GetBFTAddress(validators) }; + } + + public JObject ToJson() + { + var json = new JObject(); + json["version"] = Version; + json["index"] = Index; + json["roothash"] = RootHash.ToString(); + json["witnesses"] = Witness is null ? new JArray() : new JArray(Witness.ToJson()); + return json; + } + } +} diff --git a/src/Plugins/StateService/Network/Vote.cs b/src/Plugins/StateService/Network/Vote.cs new file mode 100644 index 0000000000..e6840c7a9f --- /dev/null +++ b/src/Plugins/StateService/Network/Vote.cs @@ -0,0 +1,40 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Vote.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using System; +using System.IO; + +namespace Neo.Plugins.StateService.Network +{ + class Vote : ISerializable + { + public int ValidatorIndex; + public uint RootIndex; + public ReadOnlyMemory Signature; + + int ISerializable.Size => sizeof(int) + sizeof(uint) + Signature.GetVarSize(); + + void ISerializable.Serialize(BinaryWriter writer) + { + writer.Write(ValidatorIndex); + writer.Write(RootIndex); + writer.WriteVarBytes(Signature.Span); + } + + void ISerializable.Deserialize(ref MemoryReader reader) + { + ValidatorIndex = reader.ReadInt32(); + RootIndex = reader.ReadUInt32(); + Signature = reader.ReadVarMemory(64); + } + } +} diff --git a/src/Plugins/StateService/README.md b/src/Plugins/StateService/README.md new file mode 100644 index 0000000000..c8e6a2ef0d --- /dev/null +++ b/src/Plugins/StateService/README.md @@ -0,0 +1,70 @@ +# StateService + +## RPC API + +### GetStateRoot +#### Params +|Name|Type|Summary|Required| +|-|-|-|-| +|Index|uint|index|true| +#### Result +StateRoot Object +|Name|Type|Summary| +|-|-|-| +|version|number|version| +|index|number|index| +|roothash|string|version| +|witness|Object|witness from validators| + +### GetProof +#### Params +|Name|Type|Summary|Required| +|-|-|-|-| +|RootHash|UInt256|state root|true| +|ScriptHash|UInt160|contract script hash|true| +|Key|base64 string|key|true| +#### Result +Proof in base64 string + +### VerifyProof +#### Params +|Name|Type|Summary| +|-|-|-| +|RootHash|UInt256|state root|true| +|Proof|base64 string|proof|true| +#### Result +Value in base64 string + +### GetStateheight +#### Result +|Name|Type|Summary| +|-|-|-| +|localrootindex|number|root hash index calculated locally| +|validatedrootindex|number|root hash index verified by validators| + +### GetState +#### Params +|Name|Type|Summary|Required| +|-|-|-|-| +|RootHash|UInt256|specify state|true| +|ScriptHash|UInt160|contract script hash|true| +|Key|base64 string|key|true| +#### Result +Value in base64 string or `null` + +### FindStates +#### Params +|Name|Type|Summary|Required| +|-|-|-|-| +|RootHash|UInt256|specify state|true| +|ScriptHash|UInt160|contract script hash|true| +|Prefix|base64 string|key prefix|true| +|From|base64 string|start key, default `Empty`|optional| +|Count|number|count of results in one request, default `MaxFindResultItems`|optional| +#### Result +|Name|Type|Summary| +|-|-|-| +|firstProof|string|proof of first value in results| +|lastProof|string|proof of last value in results| +|truncated|bool|whether the results is truncated because of limitation| +|results|array|key-values found| diff --git a/src/Plugins/StateService/Settings.cs b/src/Plugins/StateService/Settings.cs new file mode 100644 index 0000000000..8557866bc1 --- /dev/null +++ b/src/Plugins/StateService/Settings.cs @@ -0,0 +1,40 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Settings.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.Extensions.Configuration; + +namespace Neo.Plugins.StateService +{ + internal class Settings + { + public string Path { get; } + public bool FullState { get; } + public uint Network { get; } + public bool AutoVerify { get; } + public int MaxFindResultItems { get; } + + public static Settings Default { get; private set; } + + private Settings(IConfigurationSection section) + { + Path = section.GetValue("Path", "Data_MPT_{0}"); + FullState = section.GetValue("FullState", false); + Network = section.GetValue("Network", 5195086u); + AutoVerify = section.GetValue("AutoVerify", false); + MaxFindResultItems = section.GetValue("MaxFindResultItems", 100); + } + + public static void Load(IConfigurationSection section) + { + Default = new Settings(section); + } + } +} diff --git a/src/Plugins/StateService/StatePlugin.cs b/src/Plugins/StateService/StatePlugin.cs new file mode 100644 index 0000000000..1c28e92193 --- /dev/null +++ b/src/Plugins/StateService/StatePlugin.cs @@ -0,0 +1,361 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// StatePlugin.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Akka.Actor; +using Neo.ConsoleService; +using Neo.Cryptography.MPTTrie; +using Neo.IO; +using Neo.Json; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.Plugins.RpcServer; +using Neo.Plugins.StateService.Network; +using Neo.Plugins.StateService.Storage; +using Neo.Plugins.StateService.Verification; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.Wallets; +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using static Neo.Ledger.Blockchain; + +namespace Neo.Plugins.StateService +{ + public class StatePlugin : Plugin + { + public const string StatePayloadCategory = "StateService"; + public override string Name => "StateService"; + public override string Description => "Enables MPT for the node"; + public override string ConfigFile => System.IO.Path.Combine(RootPath, "StateService.json"); + + internal IActorRef Store; + internal IActorRef Verifier; + + internal static NeoSystem _system; + private IWalletProvider walletProvider; + + public StatePlugin() + { + Blockchain.Committing += OnCommitting; + Blockchain.Committed += OnCommitted; + } + + protected override void Configure() + { + Settings.Load(GetConfiguration()); + } + + protected override void OnSystemLoaded(NeoSystem system) + { + if (system.Settings.Network != Settings.Default.Network) return; + _system = system; + Store = _system.ActorSystem.ActorOf(StateStore.Props(this, string.Format(Settings.Default.Path, system.Settings.Network.ToString("X8")))); + _system.ServiceAdded += NeoSystem_ServiceAdded; + RpcServerPlugin.RegisterMethods(this, Settings.Default.Network); + } + + private void NeoSystem_ServiceAdded(object sender, object service) + { + if (service is IWalletProvider) + { + walletProvider = service as IWalletProvider; + _system.ServiceAdded -= NeoSystem_ServiceAdded; + if (Settings.Default.AutoVerify) + { + walletProvider.WalletChanged += WalletProvider_WalletChanged; + } + } + } + + private void WalletProvider_WalletChanged(object sender, Wallet wallet) + { + walletProvider.WalletChanged -= WalletProvider_WalletChanged; + Start(wallet); + } + + public override void Dispose() + { + base.Dispose(); + Blockchain.Committing -= OnCommitting; + Blockchain.Committed -= OnCommitted; + if (Store is not null) _system.EnsureStopped(Store); + if (Verifier is not null) _system.EnsureStopped(Verifier); + } + + private void OnCommitting(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) + { + if (system.Settings.Network != Settings.Default.Network) return; + StateStore.Singleton.UpdateLocalStateRootSnapshot(block.Index, snapshot.GetChangeSet().Where(p => p.State != TrackState.None).Where(p => p.Key.Id != NativeContract.Ledger.Id).ToList()); + } + + private void OnCommitted(NeoSystem system, Block block) + { + if (system.Settings.Network != Settings.Default.Network) return; + StateStore.Singleton.UpdateLocalStateRoot(block.Index); + } + + [ConsoleCommand("start states", Category = "StateService", Description = "Start as a state verifier if wallet is open")] + private void OnStartVerifyingState() + { + if (_system is null || _system.Settings.Network != Settings.Default.Network) throw new InvalidOperationException("Network doesn't match"); + Start(walletProvider.GetWallet()); + } + + public void Start(Wallet wallet) + { + if (Verifier is not null) + { + ConsoleHelper.Warning("Already started!"); + return; + } + if (wallet is null) + { + ConsoleHelper.Warning("Please open wallet first!"); + return; + } + Verifier = _system.ActorSystem.ActorOf(VerificationService.Props(wallet)); + } + + [ConsoleCommand("state root", Category = "StateService", Description = "Get state root by index")] + private void OnGetStateRoot(uint index) + { + if (_system is null || _system.Settings.Network != Settings.Default.Network) throw new InvalidOperationException("Network doesn't match"); + using var snapshot = StateStore.Singleton.GetSnapshot(); + StateRoot state_root = snapshot.GetStateRoot(index); + if (state_root is null) + ConsoleHelper.Warning("Unknown state root"); + else + ConsoleHelper.Info(state_root.ToJson().ToString()); + } + + [ConsoleCommand("state height", Category = "StateService", Description = "Get current state root index")] + private void OnGetStateHeight() + { + if (_system is null || _system.Settings.Network != Settings.Default.Network) throw new InvalidOperationException("Network doesn't match"); + ConsoleHelper.Info("LocalRootIndex: ", + $"{StateStore.Singleton.LocalRootIndex}", + " ValidatedRootIndex: ", + $"{StateStore.Singleton.ValidatedRootIndex}"); + } + + [ConsoleCommand("get proof", Category = "StateService", Description = "Get proof of key and contract hash")] + private void OnGetProof(UInt256 root_hash, UInt160 script_hash, string key) + { + if (_system is null || _system.Settings.Network != Settings.Default.Network) throw new InvalidOperationException("Network doesn't match"); + try + { + ConsoleHelper.Info("Proof: ", GetProof(root_hash, script_hash, Convert.FromBase64String(key))); + } + catch (RpcException e) + { + ConsoleHelper.Error(e.Message); + } + } + + [ConsoleCommand("verify proof", Category = "StateService", Description = "Verify proof, return value if successed")] + private void OnVerifyProof(UInt256 root_hash, string proof) + { + try + { + ConsoleHelper.Info("Verify Result: ", + VerifyProof(root_hash, Convert.FromBase64String(proof))); + } + catch (RpcException e) + { + ConsoleHelper.Error(e.Message); + } + } + + [RpcMethod] + public JToken GetStateRoot(JArray _params) + { + uint index = Result.Ok_Or(() => uint.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid state root index: {_params[0]}")); + using var snapshot = StateStore.Singleton.GetSnapshot(); + StateRoot state_root = snapshot.GetStateRoot(index).NotNull_Or(RpcError.UnknownStateRoot); + return state_root.ToJson(); + } + + private string GetProof(Trie trie, int contract_id, byte[] key) + { + StorageKey skey = new() + { + Id = contract_id, + Key = key, + }; + return GetProof(trie, skey); + } + + private string GetProof(Trie trie, StorageKey skey) + { + trie.TryGetProof(skey.ToArray(), out var proof).True_Or(RpcError.UnknownStorageItem); + using MemoryStream ms = new(); + using BinaryWriter writer = new(ms, Utility.StrictUTF8); + + writer.WriteVarBytes(skey.ToArray()); + writer.WriteVarInt(proof.Count); + foreach (var item in proof) + { + writer.WriteVarBytes(item); + } + writer.Flush(); + + return Convert.ToBase64String(ms.ToArray()); + } + + private string GetProof(UInt256 root_hash, UInt160 script_hash, byte[] key) + { + (!Settings.Default.FullState && StateStore.Singleton.CurrentLocalRootHash != root_hash).False_Or(RpcError.UnsupportedState); + using var store = StateStore.Singleton.GetStoreSnapshot(); + var trie = new Trie(store, root_hash); + var contract = GetHistoricalContractState(trie, script_hash).NotNull_Or(RpcError.UnknownContract); + return GetProof(trie, contract.Id, key); + } + + [RpcMethod] + public JToken GetProof(JArray _params) + { + UInt256 root_hash = Result.Ok_Or(() => UInt256.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid root hash: {_params[0]}")); + UInt160 script_hash = Result.Ok_Or(() => UInt160.Parse(_params[1].AsString()), RpcError.InvalidParams.WithData($"Invalid script hash: {_params[1]}")); + byte[] key = Result.Ok_Or(() => Convert.FromBase64String(_params[2].AsString()), RpcError.InvalidParams.WithData($"Invalid key: {_params[2]}")); + return GetProof(root_hash, script_hash, key); + } + + private string VerifyProof(UInt256 root_hash, byte[] proof) + { + var proofs = new HashSet(); + + using MemoryStream ms = new(proof, false); + using BinaryReader reader = new(ms, Utility.StrictUTF8); + + var key = reader.ReadVarBytes(Node.MaxKeyLength); + var count = reader.ReadVarInt(); + for (ulong i = 0; i < count; i++) + { + proofs.Add(reader.ReadVarBytes()); + } + + var value = Trie.VerifyProof(root_hash, key, proofs).NotNull_Or(RpcError.InvalidProof); + return Convert.ToBase64String(value); + } + + [RpcMethod] + public JToken VerifyProof(JArray _params) + { + UInt256 root_hash = Result.Ok_Or(() => UInt256.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid root hash: {_params[0]}")); + byte[] proof_bytes = Result.Ok_Or(() => Convert.FromBase64String(_params[1].AsString()), RpcError.InvalidParams.WithData($"Invalid proof: {_params[1]}")); + return VerifyProof(root_hash, proof_bytes); + } + + [RpcMethod] + public JToken GetStateHeight(JArray _params) + { + var json = new JObject(); + json["localrootindex"] = StateStore.Singleton.LocalRootIndex; + json["validatedrootindex"] = StateStore.Singleton.ValidatedRootIndex; + return json; + } + + private ContractState GetHistoricalContractState(Trie trie, UInt160 script_hash) + { + const byte prefix = 8; + StorageKey skey = new KeyBuilder(NativeContract.ContractManagement.Id, prefix).Add(script_hash); + return trie.TryGetValue(skey.ToArray(), out var value) ? value.AsSerializable().GetInteroperable() : null; + } + + private StorageKey ParseStorageKey(byte[] data) + { + return new() + { + Id = BinaryPrimitives.ReadInt32LittleEndian(data), + Key = data.AsMemory(sizeof(int)), + }; + } + + [RpcMethod] + public JToken FindStates(JArray _params) + { + var root_hash = Result.Ok_Or(() => UInt256.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid root hash: {_params[0]}")); + (!Settings.Default.FullState && StateStore.Singleton.CurrentLocalRootHash != root_hash).False_Or(RpcError.UnsupportedState); + var script_hash = Result.Ok_Or(() => UInt160.Parse(_params[1].AsString()), RpcError.InvalidParams.WithData($"Invalid script hash: {_params[1]}")); + var prefix = Result.Ok_Or(() => Convert.FromBase64String(_params[2].AsString()), RpcError.InvalidParams.WithData($"Invalid prefix: {_params[2]}")); + byte[] key = Array.Empty(); + if (3 < _params.Count) + key = Result.Ok_Or(() => Convert.FromBase64String(_params[3].AsString()), RpcError.InvalidParams.WithData($"Invalid key: {_params[3]}")); + int count = Settings.Default.MaxFindResultItems; + if (4 < _params.Count) + count = Result.Ok_Or(() => int.Parse(_params[4].AsString()), RpcError.InvalidParams.WithData($"Invalid count: {_params[4]}")); + if (Settings.Default.MaxFindResultItems < count) + count = Settings.Default.MaxFindResultItems; + using var store = StateStore.Singleton.GetStoreSnapshot(); + var trie = new Trie(store, root_hash); + var contract = GetHistoricalContractState(trie, script_hash).NotNull_Or(RpcError.UnknownContract); + StorageKey pkey = new() + { + Id = contract.Id, + Key = prefix, + }; + StorageKey fkey = new() + { + Id = pkey.Id, + Key = key, + }; + JObject json = new(); + JArray jarr = new(); + int i = 0; + foreach (var (ikey, ivalue) in trie.Find(pkey.ToArray(), 0 < key.Length ? fkey.ToArray() : null)) + { + if (count < i) break; + if (i < count) + { + JObject j = new(); + j["key"] = Convert.ToBase64String(ParseStorageKey(ikey.ToArray()).Key.Span); + j["value"] = Convert.ToBase64String(ivalue.Span); + jarr.Add(j); + } + i++; + }; + if (0 < jarr.Count) + { + json["firstProof"] = GetProof(trie, contract.Id, Convert.FromBase64String(jarr.First()["key"].AsString())); + } + if (1 < jarr.Count) + { + json["lastProof"] = GetProof(trie, contract.Id, Convert.FromBase64String(jarr.Last()["key"].AsString())); + } + json["truncated"] = count < i; + json["results"] = jarr; + return json; + } + + [RpcMethod] + public JToken GetState(JArray _params) + { + var root_hash = Result.Ok_Or(() => UInt256.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid root hash: {_params[0]}")); + (!Settings.Default.FullState && StateStore.Singleton.CurrentLocalRootHash != root_hash).False_Or(RpcError.UnsupportedState); + var script_hash = Result.Ok_Or(() => UInt160.Parse(_params[1].AsString()), RpcError.InvalidParams.WithData($"Invalid script hash: {_params[1]}")); + var key = Result.Ok_Or(() => Convert.FromBase64String(_params[2].AsString()), RpcError.InvalidParams.WithData($"Invalid key: {_params[2]}")); + using var store = StateStore.Singleton.GetStoreSnapshot(); + var trie = new Trie(store, root_hash); + + var contract = GetHistoricalContractState(trie, script_hash).NotNull_Or(RpcError.UnknownContract); + StorageKey skey = new() + { + Id = contract.Id, + Key = key, + }; + return Convert.ToBase64String(trie[skey.ToArray()]); + } + } +} diff --git a/src/Plugins/StateService/StateService.csproj b/src/Plugins/StateService/StateService.csproj new file mode 100644 index 0000000000..58c18ac498 --- /dev/null +++ b/src/Plugins/StateService/StateService.csproj @@ -0,0 +1,22 @@ + + + + net8.0 + Neo.Plugins.StateService + true + ../../../bin/$(PackageId) + + + + + + + + + + + PreserveNewest + + + + diff --git a/src/Plugins/StateService/StateService.json b/src/Plugins/StateService/StateService.json new file mode 100644 index 0000000000..265436fc30 --- /dev/null +++ b/src/Plugins/StateService/StateService.json @@ -0,0 +1,12 @@ +{ + "PluginConfiguration": { + "Path": "Data_MPT_{0}", + "FullState": false, + "Network": 860833102, + "AutoVerify": false, + "MaxFindResultItems": 100 + }, + "Dependency": [ + "RpcServer" + ] +} diff --git a/src/Plugins/StateService/Storage/Keys.cs b/src/Plugins/StateService/Storage/Keys.cs new file mode 100644 index 0000000000..4ffecef48e --- /dev/null +++ b/src/Plugins/StateService/Storage/Keys.cs @@ -0,0 +1,30 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Keys.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Buffers.Binary; + +namespace Neo.Plugins.StateService.Storage +{ + public static class Keys + { + public static byte[] StateRoot(uint index) + { + byte[] buffer = new byte[sizeof(uint) + 1]; + buffer[0] = 1; + BinaryPrimitives.WriteUInt32BigEndian(buffer.AsSpan(1), index); + return buffer; + } + + public static readonly byte[] CurrentLocalRootIndex = { 0x02 }; + public static readonly byte[] CurrentValidatedRootIndex = { 0x04 }; + } +} diff --git a/src/Plugins/StateService/Storage/StateSnapshot.cs b/src/Plugins/StateService/Storage/StateSnapshot.cs new file mode 100644 index 0000000000..70ec006227 --- /dev/null +++ b/src/Plugins/StateService/Storage/StateSnapshot.cs @@ -0,0 +1,92 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// StateSnapshot.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Cryptography.MPTTrie; +using Neo.IO; +using Neo.Persistence; +using Neo.Plugins.StateService.Network; +using System; + +namespace Neo.Plugins.StateService.Storage +{ + class StateSnapshot : IDisposable + { + private readonly ISnapshot snapshot; + public Trie Trie; + + public StateSnapshot(IStore store) + { + snapshot = store.GetSnapshot(); + Trie = new Trie(snapshot, CurrentLocalRootHash(), Settings.Default.FullState); + } + + public StateRoot GetStateRoot(uint index) + { + return snapshot.TryGet(Keys.StateRoot(index))?.AsSerializable(); + } + + public void AddLocalStateRoot(StateRoot state_root) + { + snapshot.Put(Keys.StateRoot(state_root.Index), state_root.ToArray()); + snapshot.Put(Keys.CurrentLocalRootIndex, BitConverter.GetBytes(state_root.Index)); + } + + public uint? CurrentLocalRootIndex() + { + var bytes = snapshot.TryGet(Keys.CurrentLocalRootIndex); + if (bytes is null) return null; + return BitConverter.ToUInt32(bytes); + } + + public UInt256 CurrentLocalRootHash() + { + var index = CurrentLocalRootIndex(); + if (index is null) return null; + return GetStateRoot((uint)index)?.RootHash; + } + + public void AddValidatedStateRoot(StateRoot state_root) + { + if (state_root?.Witness is null) + throw new ArgumentException(nameof(state_root) + " missing witness in invalidated state root"); + snapshot.Put(Keys.StateRoot(state_root.Index), state_root.ToArray()); + snapshot.Put(Keys.CurrentValidatedRootIndex, BitConverter.GetBytes(state_root.Index)); + } + + public uint? CurrentValidatedRootIndex() + { + var bytes = snapshot.TryGet(Keys.CurrentValidatedRootIndex); + if (bytes is null) return null; + return BitConverter.ToUInt32(bytes); + } + + public UInt256 CurrentValidatedRootHash() + { + var index = CurrentLocalRootIndex(); + if (index is null) return null; + var state_root = GetStateRoot((uint)index); + if (state_root is null || state_root.Witness is null) + throw new InvalidOperationException(nameof(CurrentValidatedRootHash) + " could not get validated state root"); + return state_root.RootHash; + } + + public void Commit() + { + Trie.Commit(); + snapshot.Commit(); + } + + public void Dispose() + { + snapshot.Dispose(); + } + } +} diff --git a/src/Plugins/StateService/Storage/StateStore.cs b/src/Plugins/StateService/Storage/StateStore.cs new file mode 100644 index 0000000000..f2ca6d7d05 --- /dev/null +++ b/src/Plugins/StateService/Storage/StateStore.cs @@ -0,0 +1,188 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// StateStore.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Akka.Actor; +using Neo.IO; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.Plugins.StateService.Network; +using Neo.Plugins.StateService.Verification; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Neo.Plugins.StateService.Storage +{ + class StateStore : UntypedActor + { + private readonly StatePlugin system; + private readonly IStore store; + private const int MaxCacheCount = 100; + private readonly Dictionary cache = new Dictionary(); + private StateSnapshot currentSnapshot; + private StateSnapshot _state_snapshot; + public UInt256 CurrentLocalRootHash => currentSnapshot.CurrentLocalRootHash(); + public uint? LocalRootIndex => currentSnapshot.CurrentLocalRootIndex(); + public uint? ValidatedRootIndex => currentSnapshot.CurrentValidatedRootIndex(); + + private static StateStore singleton; + public static StateStore Singleton + { + get + { + while (singleton is null) Thread.Sleep(10); + return singleton; + } + } + + public StateStore(StatePlugin system, string path) + { + if (singleton != null) throw new InvalidOperationException(nameof(StateStore)); + this.system = system; + store = StatePlugin._system.LoadStore(path); + singleton = this; + StatePlugin._system.ActorSystem.EventStream.Subscribe(Self, typeof(Blockchain.RelayResult)); + UpdateCurrentSnapshot(); + } + + public void Dispose() + { + store.Dispose(); + } + + public StateSnapshot GetSnapshot() + { + return new StateSnapshot(store); + } + + public ISnapshot GetStoreSnapshot() + { + return store.GetSnapshot(); + } + + protected override void OnReceive(object message) + { + switch (message) + { + case StateRoot state_root: + OnNewStateRoot(state_root); + break; + case Blockchain.RelayResult rr: + if (rr.Result == VerifyResult.Succeed && rr.Inventory is ExtensiblePayload payload && payload.Category == StatePlugin.StatePayloadCategory) + OnStatePayload(payload); + break; + default: + break; + } + } + + private void OnStatePayload(ExtensiblePayload payload) + { + if (payload.Data.Length == 0) return; + if ((MessageType)payload.Data.Span[0] != MessageType.StateRoot) return; + StateRoot message; + try + { + message = payload.Data[1..].AsSerializable(); + } + catch (FormatException) + { + return; + } + OnNewStateRoot(message); + } + + private bool OnNewStateRoot(StateRoot state_root) + { + if (state_root?.Witness is null) return false; + if (ValidatedRootIndex != null && state_root.Index <= ValidatedRootIndex) return false; + if (LocalRootIndex is null) throw new InvalidOperationException(nameof(StateStore) + " could not get local root index"); + if (LocalRootIndex < state_root.Index && state_root.Index < LocalRootIndex + MaxCacheCount) + { + cache.Add(state_root.Index, state_root); + return true; + } + using var state_snapshot = Singleton.GetSnapshot(); + StateRoot local_root = state_snapshot.GetStateRoot(state_root.Index); + if (local_root is null || local_root.Witness != null) return false; + if (!state_root.Verify(StatePlugin._system.Settings, StatePlugin._system.StoreView)) return false; + if (local_root.RootHash != state_root.RootHash) return false; + state_snapshot.AddValidatedStateRoot(state_root); + state_snapshot.Commit(); + UpdateCurrentSnapshot(); + system.Verifier?.Tell(new VerificationService.ValidatedRootPersisted { Index = state_root.Index }); + return true; + } + + public void UpdateLocalStateRootSnapshot(uint height, List change_set) + { + _state_snapshot = Singleton.GetSnapshot(); + foreach (var item in change_set) + { + switch (item.State) + { + case TrackState.Added: + _state_snapshot.Trie.Put(item.Key.ToArray(), item.Item.ToArray()); + break; + case TrackState.Changed: + _state_snapshot.Trie.Put(item.Key.ToArray(), item.Item.ToArray()); + break; + case TrackState.Deleted: + _state_snapshot.Trie.Delete(item.Key.ToArray()); + break; + } + } + UInt256 root_hash = _state_snapshot.Trie.Root.Hash; + StateRoot state_root = new StateRoot + { + Version = StateRoot.CurrentVersion, + Index = height, + RootHash = root_hash, + Witness = null, + }; + _state_snapshot.AddLocalStateRoot(state_root); + } + + public void UpdateLocalStateRoot(uint height) + { + _state_snapshot?.Commit(); + _state_snapshot = null; + UpdateCurrentSnapshot(); + system.Verifier?.Tell(new VerificationService.BlockPersisted { Index = height }); + CheckValidatedStateRoot(height); + } + + private void CheckValidatedStateRoot(uint index) + { + if (cache.TryGetValue(index, out StateRoot state_root)) + { + cache.Remove(index); + Self.Tell(state_root); + } + } + + private void UpdateCurrentSnapshot() + { + Interlocked.Exchange(ref currentSnapshot, GetSnapshot())?.Dispose(); + } + + protected override void PostStop() + { + base.PostStop(); + } + + public static Props Props(StatePlugin system, string path) + { + return Akka.Actor.Props.Create(() => new StateStore(system, path)); + } + } +} diff --git a/src/Plugins/StateService/Verification/VerificationContext.cs b/src/Plugins/StateService/Verification/VerificationContext.cs new file mode 100644 index 0000000000..feaf591795 --- /dev/null +++ b/src/Plugins/StateService/Verification/VerificationContext.cs @@ -0,0 +1,177 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// VerificationContext.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Akka.Actor; +using Neo.Cryptography; +using Neo.Cryptography.ECC; +using Neo.IO; +using Neo.Network.P2P; +using Neo.Network.P2P.Payloads; +using Neo.Plugins.StateService.Network; +using Neo.Plugins.StateService.Storage; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.Wallets; +using System.Collections.Concurrent; +using System.IO; + +namespace Neo.Plugins.StateService.Verification +{ + class VerificationContext + { + private const uint MaxValidUntilBlockIncrement = 100; + private StateRoot root; + private ExtensiblePayload rootPayload; + private ExtensiblePayload votePayload; + private readonly Wallet wallet; + private readonly KeyPair keyPair; + private readonly int myIndex; + private readonly uint rootIndex; + private readonly ECPoint[] verifiers; + private int M => verifiers.Length - (verifiers.Length - 1) / 3; + private readonly ConcurrentDictionary signatures = new ConcurrentDictionary(); + + public int Retries; + public bool IsValidator => myIndex >= 0; + public int MyIndex => myIndex; + public uint RootIndex => rootIndex; + public ECPoint[] Verifiers => verifiers; + public int Sender + { + get + { + int p = ((int)rootIndex - Retries) % verifiers.Length; + return p >= 0 ? p : p + verifiers.Length; + } + } + public bool IsSender => myIndex == Sender; + public ICancelable Timer; + public StateRoot StateRoot + { + get + { + if (root is null) + { + using var snapshot = StateStore.Singleton.GetSnapshot(); + root = snapshot.GetStateRoot(rootIndex); + } + return root; + } + } + public ExtensiblePayload StateRootMessage => rootPayload; + public ExtensiblePayload VoteMessage + { + get + { + if (votePayload is null) + votePayload = CreateVoteMessage(); + return votePayload; + } + } + + public VerificationContext(Wallet wallet, uint index) + { + this.wallet = wallet; + Retries = 0; + myIndex = -1; + rootIndex = index; + verifiers = NativeContract.RoleManagement.GetDesignatedByRole(StatePlugin._system.StoreView, Role.StateValidator, index); + if (wallet is null) return; + for (int i = 0; i < verifiers.Length; i++) + { + WalletAccount account = wallet.GetAccount(verifiers[i]); + if (account?.HasKey != true) continue; + myIndex = i; + keyPair = account.GetKey(); + break; + } + } + + private ExtensiblePayload CreateVoteMessage() + { + if (StateRoot is null) return null; + if (!signatures.TryGetValue(myIndex, out byte[] sig)) + { + sig = StateRoot.Sign(keyPair, StatePlugin._system.Settings.Network); + signatures[myIndex] = sig; + } + return CreatePayload(MessageType.Vote, new Vote + { + RootIndex = rootIndex, + ValidatorIndex = myIndex, + Signature = sig + }, VerificationService.MaxCachedVerificationProcessCount); + } + + public bool AddSignature(int index, byte[] sig) + { + if (M <= signatures.Count) return false; + if (index < 0 || verifiers.Length <= index) return false; + if (signatures.ContainsKey(index)) return false; + Utility.Log(nameof(VerificationContext), LogLevel.Info, $"vote received, height={rootIndex}, index={index}"); + ECPoint validator = verifiers[index]; + byte[] hash_data = StateRoot?.GetSignData(StatePlugin._system.Settings.Network); + if (hash_data is null || !Crypto.VerifySignature(hash_data, sig, validator)) + { + Utility.Log(nameof(VerificationContext), LogLevel.Info, "incorrect vote, invalid signature"); + return false; + } + return signatures.TryAdd(index, sig); + } + + public bool CheckSignatures() + { + if (StateRoot is null) return false; + if (signatures.Count < M) return false; + if (StateRoot.Witness is null) + { + Contract contract = Contract.CreateMultiSigContract(M, verifiers); + ContractParametersContext sc = new(StatePlugin._system.StoreView, StateRoot, StatePlugin._system.Settings.Network); + for (int i = 0, j = 0; i < verifiers.Length && j < M; i++) + { + if (!signatures.TryGetValue(i, out byte[] sig)) continue; + sc.AddSignature(contract, verifiers[i], sig); + j++; + } + if (!sc.Completed) return false; + StateRoot.Witness = sc.GetWitnesses()[0]; + } + if (IsSender) + rootPayload = CreatePayload(MessageType.StateRoot, StateRoot, MaxValidUntilBlockIncrement); + return true; + } + + private ExtensiblePayload CreatePayload(MessageType type, ISerializable payload, uint validBlockEndThreshold) + { + byte[] data; + using (MemoryStream ms = new MemoryStream()) + using (BinaryWriter writer = new BinaryWriter(ms)) + { + writer.Write((byte)type); + payload.Serialize(writer); + writer.Flush(); + data = ms.ToArray(); + } + ExtensiblePayload msg = new ExtensiblePayload + { + Category = StatePlugin.StatePayloadCategory, + ValidBlockStart = StateRoot.Index, + ValidBlockEnd = StateRoot.Index + validBlockEndThreshold, + Sender = Contract.CreateSignatureRedeemScript(verifiers[MyIndex]).ToScriptHash(), + Data = data, + }; + ContractParametersContext sc = new ContractParametersContext(StatePlugin._system.StoreView, msg, StatePlugin._system.Settings.Network); + wallet.Sign(sc); + msg.Witness = sc.GetWitnesses()[0]; + return msg; + } + } +} diff --git a/src/Plugins/StateService/Verification/VerificationService.cs b/src/Plugins/StateService/Verification/VerificationService.cs new file mode 100644 index 0000000000..a8eb9fd030 --- /dev/null +++ b/src/Plugins/StateService/Verification/VerificationService.cs @@ -0,0 +1,170 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// VerificationService.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Akka.Actor; +using Akka.Util.Internal; +using Neo.IO; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Plugins.StateService.Network; +using Neo.Wallets; +using System; +using System.Collections.Concurrent; +using System.Linq; + +namespace Neo.Plugins.StateService.Verification +{ + public class VerificationService : UntypedActor + { + public class ValidatedRootPersisted { public uint Index; } + public class BlockPersisted { public uint Index; } + public const int MaxCachedVerificationProcessCount = 10; + private class Timer { public uint Index; } + private static readonly uint TimeoutMilliseconds = StatePlugin._system.Settings.MillisecondsPerBlock; + private static readonly uint DelayMilliseconds = 3000; + private readonly Wallet wallet; + private readonly ConcurrentDictionary contexts = new ConcurrentDictionary(); + + public VerificationService(Wallet wallet) + { + this.wallet = wallet; + StatePlugin._system.ActorSystem.EventStream.Subscribe(Self, typeof(Blockchain.RelayResult)); + } + + private void SendVote(VerificationContext context) + { + if (context.VoteMessage is null) return; + Utility.Log(nameof(VerificationService), LogLevel.Info, $"relay vote, height={context.RootIndex}, retry={context.Retries}"); + StatePlugin._system.Blockchain.Tell(context.VoteMessage); + } + + private void OnStateRootVote(Vote vote) + { + if (contexts.TryGetValue(vote.RootIndex, out VerificationContext context) && context.AddSignature(vote.ValidatorIndex, vote.Signature.ToArray())) + { + CheckVotes(context); + } + } + + private void CheckVotes(VerificationContext context) + { + if (context.IsSender && context.CheckSignatures()) + { + if (context.StateRootMessage is null) return; + Utility.Log(nameof(VerificationService), LogLevel.Info, $"relay state root, height={context.StateRoot.Index}, root={context.StateRoot.RootHash}"); + StatePlugin._system.Blockchain.Tell(context.StateRootMessage); + } + } + + private void OnBlockPersisted(uint index) + { + if (MaxCachedVerificationProcessCount <= contexts.Count) + { + contexts.Keys.OrderBy(p => p).Take(contexts.Count - MaxCachedVerificationProcessCount + 1).ForEach(p => + { + if (contexts.TryRemove(p, out var value)) + { + value.Timer.CancelIfNotNull(); + } + }); + } + var p = new VerificationContext(wallet, index); + if (p.IsValidator && contexts.TryAdd(index, p)) + { + p.Timer = Context.System.Scheduler.ScheduleTellOnceCancelable(TimeSpan.FromMilliseconds(DelayMilliseconds), Self, new Timer + { + Index = index, + }, ActorRefs.NoSender); + Utility.Log(nameof(VerificationContext), LogLevel.Info, $"new validate process, height={index}, index={p.MyIndex}, ongoing={contexts.Count}"); + } + } + + private void OnValidatedRootPersisted(uint index) + { + Utility.Log(nameof(VerificationService), LogLevel.Info, $"persisted state root, height={index}"); + foreach (var i in contexts.Where(i => i.Key <= index)) + { + if (contexts.TryRemove(i.Key, out var value)) + { + value.Timer.CancelIfNotNull(); + } + } + } + + private void OnTimer(uint index) + { + if (contexts.TryGetValue(index, out VerificationContext context)) + { + SendVote(context); + CheckVotes(context); + context.Timer.CancelIfNotNull(); + context.Timer = Context.System.Scheduler.ScheduleTellOnceCancelable(TimeSpan.FromMilliseconds(TimeoutMilliseconds << context.Retries), Self, new Timer + { + Index = index, + }, ActorRefs.NoSender); + context.Retries++; + } + } + + private void OnVoteMessage(ExtensiblePayload payload) + { + if (payload.Data.Length == 0) return; + if ((MessageType)payload.Data.Span[0] != MessageType.Vote) return; + Vote message; + try + { + message = payload.Data[1..].AsSerializable(); + } + catch (FormatException) + { + return; + } + OnStateRootVote(message); + } + + protected override void OnReceive(object message) + { + switch (message) + { + case Vote v: + OnStateRootVote(v); + break; + case BlockPersisted bp: + OnBlockPersisted(bp.Index); + break; + case ValidatedRootPersisted root: + OnValidatedRootPersisted(root.Index); + break; + case Timer timer: + OnTimer(timer.Index); + break; + case Blockchain.RelayResult rr: + if (rr.Result == VerifyResult.Succeed && rr.Inventory is ExtensiblePayload payload && payload.Category == StatePlugin.StatePayloadCategory) + { + OnVoteMessage(payload); + } + break; + default: + break; + } + } + + protected override void PostStop() + { + base.PostStop(); + } + + public static Props Props(Wallet wallet) + { + return Akka.Actor.Props.Create(() => new VerificationService(wallet)); + } + } +} diff --git a/src/Plugins/StorageDumper/Settings.cs b/src/Plugins/StorageDumper/Settings.cs new file mode 100644 index 0000000000..c2761ce6b9 --- /dev/null +++ b/src/Plugins/StorageDumper/Settings.cs @@ -0,0 +1,51 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Settings.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.Extensions.Configuration; +using Neo.SmartContract.Native; + +namespace Neo.Plugins.StorageDumper +{ + internal class Settings + { + /// + /// Amount of storages states (heights) to be dump in a given json file + /// + public uint BlockCacheSize { get; } + /// + /// Height to begin storage dump + /// + public uint HeightToBegin { get; } + /// + /// Default number of items per folder + /// + public uint StoragePerFolder { get; } + public IReadOnlyList Exclude { get; } + + public static Settings? Default { get; private set; } + + private Settings(IConfigurationSection section) + { + // Geting settings for storage changes state dumper + BlockCacheSize = section.GetValue("BlockCacheSize", 1000u); + HeightToBegin = section.GetValue("HeightToBegin", 0u); + StoragePerFolder = section.GetValue("StoragePerFolder", 100000u); + Exclude = section.GetSection("Exclude").Exists() + ? section.GetSection("Exclude").GetChildren().Select(p => int.Parse(p.Value!)).ToArray() + : new[] { NativeContract.Ledger.Id }; + } + + public static void Load(IConfigurationSection section) + { + Default = new Settings(section); + } + } +} diff --git a/src/Plugins/StorageDumper/StorageDumper.cs b/src/Plugins/StorageDumper/StorageDumper.cs new file mode 100644 index 0000000000..d8987cdcaa --- /dev/null +++ b/src/Plugins/StorageDumper/StorageDumper.cs @@ -0,0 +1,184 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// StorageDumper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.ConsoleService; +using Neo.IO; +using Neo.Json; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.SmartContract.Native; + +namespace Neo.Plugins.StorageDumper +{ + public class StorageDumper : Plugin + { + private readonly Dictionary systems = new Dictionary(); + + private StreamWriter? _writer; + /// + /// _currentBlock stores the last cached item + /// + private JObject? _currentBlock; + private string? _lastCreateDirectory; + + + public override string Description => "Exports Neo-CLI status data"; + + public override string ConfigFile => System.IO.Path.Combine(RootPath, "StorageDumper.json"); + + public StorageDumper() + { + Blockchain.Committing += OnCommitting; + Blockchain.Committed += OnCommitted; + } + + public override void Dispose() + { + Blockchain.Committing -= OnCommitting; + Blockchain.Committed -= OnCommitted; + } + + protected override void Configure() + { + Settings.Load(GetConfiguration()); + } + + protected override void OnSystemLoaded(NeoSystem system) + { + systems.Add(system.Settings.Network, system); + } + + /// + /// Process "dump contract-storage" command + /// + [ConsoleCommand("dump contract-storage", Category = "Storage", Description = "You can specify the contract script hash or use null to get the corresponding information from the storage")] + private void OnDumpStorage(uint network, UInt160? contractHash = null) + { + if (!systems.ContainsKey(network)) throw new InvalidOperationException("invalid network"); + string path = $"dump_{network}.json"; + byte[]? prefix = null; + if (contractHash is not null) + { + var contract = NativeContract.ContractManagement.GetContract(systems[network].StoreView, contractHash); + if (contract is null) throw new InvalidOperationException("contract not found"); + prefix = BitConverter.GetBytes(contract.Id); + } + var states = systems[network].StoreView.Find(prefix); + JArray array = new JArray(states.Where(p => !Settings.Default!.Exclude.Contains(p.Key.Id)).Select(p => new JObject + { + ["key"] = Convert.ToBase64String(p.Key.ToArray()), + ["value"] = Convert.ToBase64String(p.Value.ToArray()) + })); + File.WriteAllText(path, array.ToString()); + ConsoleHelper.Info("States", + $"({array.Count})", + " have been dumped into file ", + $"{path}"); + } + + private void OnCommitting(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) + { + InitFileWriter(system.Settings.Network, snapshot); + OnPersistStorage(system.Settings.Network, snapshot); + } + + private void OnPersistStorage(uint network, DataCache snapshot) + { + uint blockIndex = NativeContract.Ledger.CurrentIndex(snapshot); + if (blockIndex >= Settings.Default!.HeightToBegin) + { + JArray stateChangeArray = new JArray(); + + foreach (var trackable in snapshot.GetChangeSet()) + { + if (Settings.Default.Exclude.Contains(trackable.Key.Id)) + continue; + JObject state = new JObject(); + switch (trackable.State) + { + case TrackState.Added: + state["state"] = "Added"; + state["key"] = Convert.ToBase64String(trackable.Key.ToArray()); + state["value"] = Convert.ToBase64String(trackable.Item.ToArray()); + break; + case TrackState.Changed: + state["state"] = "Changed"; + state["key"] = Convert.ToBase64String(trackable.Key.ToArray()); + state["value"] = Convert.ToBase64String(trackable.Item.ToArray()); + break; + case TrackState.Deleted: + state["state"] = "Deleted"; + state["key"] = Convert.ToBase64String(trackable.Key.ToArray()); + break; + } + stateChangeArray.Add(state); + } + + JObject bs_item = new JObject(); + bs_item["block"] = blockIndex; + bs_item["size"] = stateChangeArray.Count; + bs_item["storage"] = stateChangeArray; + _currentBlock = bs_item; + } + } + + + private void OnCommitted(NeoSystem system, Block block) + { + OnCommitStorage(system.Settings.Network, system.StoreView); + } + + void OnCommitStorage(uint network, DataCache snapshot) + { + if (_currentBlock != null && _writer != null) + { + _writer.WriteLine(_currentBlock.ToString()); + _writer.Flush(); + } + } + + private void InitFileWriter(uint network, DataCache snapshot) + { + uint blockIndex = NativeContract.Ledger.CurrentIndex(snapshot); + if (_writer == null + || blockIndex % Settings.Default!.BlockCacheSize == 0) + { + string path = GetOrCreateDirectory(network, blockIndex); + var filepart = (blockIndex / Settings.Default!.BlockCacheSize) * Settings.Default.BlockCacheSize; + path = $"{path}/dump-block-{filepart}.dump"; + if (_writer != null) + { + _writer.Dispose(); + } + _writer = new StreamWriter(new FileStream(path, FileMode.Append)); + } + } + + private string GetOrCreateDirectory(uint network, uint blockIndex) + { + string dirPathWithBlock = GetDirectoryPath(network, blockIndex); + if (_lastCreateDirectory != dirPathWithBlock) + { + Directory.CreateDirectory(dirPathWithBlock); + _lastCreateDirectory = dirPathWithBlock; + } + return dirPathWithBlock; + } + + private string GetDirectoryPath(uint network, uint blockIndex) + { + uint folder = (blockIndex / Settings.Default!.StoragePerFolder) * Settings.Default.StoragePerFolder; + return $"./StorageDumper_{network}/BlockStorage_{folder}"; + } + + } +} diff --git a/src/Plugins/StorageDumper/StorageDumper.csproj b/src/Plugins/StorageDumper/StorageDumper.csproj new file mode 100644 index 0000000000..ebadda6136 --- /dev/null +++ b/src/Plugins/StorageDumper/StorageDumper.csproj @@ -0,0 +1,21 @@ + + + + net8.0 + Neo.Plugins.StorageDumper + enable + enable + ../../../bin/$(PackageId) + + + + + + + + + PreserveNewest + + + + diff --git a/src/Plugins/StorageDumper/StorageDumper.json b/src/Plugins/StorageDumper/StorageDumper.json new file mode 100644 index 0000000000..b327c37e0c --- /dev/null +++ b/src/Plugins/StorageDumper/StorageDumper.json @@ -0,0 +1,8 @@ +{ + "PluginConfiguration": { + "BlockCacheSize": 1000, + "HeightToBegin": 0, + "StoragePerFolder": 100000, + "Exclude": [ -4 ] + } +} diff --git a/src/Plugins/TokensTracker/Extensions.cs b/src/Plugins/TokensTracker/Extensions.cs new file mode 100644 index 0000000000..7805056280 --- /dev/null +++ b/src/Plugins/TokensTracker/Extensions.cs @@ -0,0 +1,67 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Extensions.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using Neo.Persistence; +using Neo.VM.Types; +using System; +using System.Collections.Generic; +using System.Numerics; + +namespace Neo.Plugins +{ + public static class Extensions + { + public static bool NotNull(this StackItem item) + { + return !item.IsNull; + } + + public static string ToBase64(this ReadOnlySpan item) + { + return item == null ? String.Empty : Convert.ToBase64String(item); + } + + public static int GetVarSize(this ByteString item) + { + var length = item.GetSpan().Length; + return IO.Helper.GetVarSize(length) + length; + } + + public static int GetVarSize(this BigInteger item) + { + var length = item.GetByteCount(); + return IO.Helper.GetVarSize(length) + length; + } + + public static IEnumerable<(TKey, TValue)> FindPrefix(this IStore db, byte[] prefix) + where TKey : ISerializable, new() + where TValue : class, ISerializable, new() + { + foreach (var (key, value) in db.Seek(prefix, SeekDirection.Forward)) + { + if (!key.AsSpan().StartsWith(prefix)) break; + yield return (key.AsSerializable(1), value.AsSerializable()); + } + } + + public static IEnumerable<(TKey, TValue)> FindRange(this IStore db, byte[] startKey, byte[] endKey) + where TKey : ISerializable, new() + where TValue : class, ISerializable, new() + { + foreach (var (key, value) in db.Seek(startKey, SeekDirection.Forward)) + { + if (key.AsSpan().SequenceCompareTo(endKey) > 0) break; + yield return (key.AsSerializable(1), value.AsSerializable()); + } + } + } +} diff --git a/src/Plugins/TokensTracker/TokensTracker.cs b/src/Plugins/TokensTracker/TokensTracker.cs new file mode 100644 index 0000000000..eacd0de6e4 --- /dev/null +++ b/src/Plugins/TokensTracker/TokensTracker.cs @@ -0,0 +1,103 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TokensTracker.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.Extensions.Configuration; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.Plugins.RpcServer; +using Neo.Plugins.Trackers; +using System.Collections.Generic; +using System.Linq; +using static System.IO.Path; + +namespace Neo.Plugins +{ + public class TokensTracker : Plugin + { + private string _dbPath; + private bool _shouldTrackHistory; + private uint _maxResults; + private uint _network; + private string[] _enabledTrackers; + private IStore _db; + private NeoSystem neoSystem; + private readonly List trackers = new(); + + public override string Description => "Enquiries balances and transaction history of accounts through RPC"; + + public override string ConfigFile => System.IO.Path.Combine(RootPath, "TokensTracker.json"); + + public TokensTracker() + { + Blockchain.Committing += OnCommitting; + Blockchain.Committed += OnCommitted; + } + + public override void Dispose() + { + Blockchain.Committing -= OnCommitting; + Blockchain.Committed -= OnCommitted; + } + + protected override void Configure() + { + IConfigurationSection config = GetConfiguration(); + _dbPath = config.GetValue("DBPath", "TokensBalanceData"); + _shouldTrackHistory = config.GetValue("TrackHistory", true); + _maxResults = config.GetValue("MaxResults", 1000u); + _network = config.GetValue("Network", 860833102u); + _enabledTrackers = config.GetSection("EnabledTrackers").GetChildren().Select(p => p.Value).ToArray(); + } + + protected override void OnSystemLoaded(NeoSystem system) + { + if (system.Settings.Network != _network) return; + neoSystem = system; + string path = string.Format(_dbPath, neoSystem.Settings.Network.ToString("X8")); + _db = neoSystem.LoadStore(GetFullPath(path)); + if (_enabledTrackers.Contains("NEP-11")) + trackers.Add(new Trackers.NEP_11.Nep11Tracker(_db, _maxResults, _shouldTrackHistory, neoSystem)); + if (_enabledTrackers.Contains("NEP-17")) + trackers.Add(new Trackers.NEP_17.Nep17Tracker(_db, _maxResults, _shouldTrackHistory, neoSystem)); + foreach (TrackerBase tracker in trackers) + RpcServerPlugin.RegisterMethods(tracker, _network); + } + + private void ResetBatch() + { + foreach (var tracker in trackers) + { + tracker.ResetBatch(); + } + } + + private void OnCommitting(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) + { + if (system.Settings.Network != _network) return; + // Start freshly with a new DBCache for each block. + ResetBatch(); + foreach (var tracker in trackers) + { + tracker.OnPersist(system, block, snapshot, applicationExecutedList); + } + } + + private void OnCommitted(NeoSystem system, Block block) + { + if (system.Settings.Network != _network) return; + foreach (var tracker in trackers) + { + tracker.Commit(); + } + } + } +} diff --git a/src/Plugins/TokensTracker/TokensTracker.csproj b/src/Plugins/TokensTracker/TokensTracker.csproj new file mode 100644 index 0000000000..5f17120159 --- /dev/null +++ b/src/Plugins/TokensTracker/TokensTracker.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Neo.Plugins.TokensTracker + ../../../bin/$(PackageId) + + + + + + + + + PreserveNewest + + + + diff --git a/src/Plugins/TokensTracker/TokensTracker.json b/src/Plugins/TokensTracker/TokensTracker.json new file mode 100644 index 0000000000..ca63183b68 --- /dev/null +++ b/src/Plugins/TokensTracker/TokensTracker.json @@ -0,0 +1,12 @@ +{ + "PluginConfiguration": { + "DBPath": "TokenBalanceData", + "TrackHistory": true, + "MaxResults": 1000, + "Network": 860833102, + "EnabledTrackers": [ "NEP-11", "NEP-17" ] + }, + "Dependency": [ + "RpcServer" + ] +} diff --git a/src/Plugins/TokensTracker/Trackers/NEP-11/Nep11BalanceKey.cs b/src/Plugins/TokensTracker/Trackers/NEP-11/Nep11BalanceKey.cs new file mode 100644 index 0000000000..1f375f33d9 --- /dev/null +++ b/src/Plugins/TokensTracker/Trackers/NEP-11/Nep11BalanceKey.cs @@ -0,0 +1,81 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Nep11BalanceKey.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using Neo.VM.Types; +using System; +using System.IO; + +namespace Neo.Plugins.Trackers.NEP_11 +{ + public class Nep11BalanceKey : IComparable, IEquatable, ISerializable + { + public readonly UInt160 UserScriptHash; + public readonly UInt160 AssetScriptHash; + public ByteString Token; + public int Size => UInt160.Length + UInt160.Length + Token.GetVarSize(); + + public Nep11BalanceKey() : this(new UInt160(), new UInt160(), ByteString.Empty) + { + } + + public Nep11BalanceKey(UInt160 userScriptHash, UInt160 assetScriptHash, ByteString tokenId) + { + if (userScriptHash == null || assetScriptHash == null || tokenId == null) + throw new ArgumentNullException(); + UserScriptHash = userScriptHash; + AssetScriptHash = assetScriptHash; + Token = tokenId; + } + + public int CompareTo(Nep11BalanceKey other) + { + if (other is null) return 1; + if (ReferenceEquals(this, other)) return 0; + int result = UserScriptHash.CompareTo(other.UserScriptHash); + if (result != 0) return result; + result = AssetScriptHash.CompareTo(other.AssetScriptHash); + if (result != 0) return result; + return (Token.GetInteger() - other.Token.GetInteger()).Sign; + } + + public bool Equals(Nep11BalanceKey other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return UserScriptHash.Equals(other.UserScriptHash) && AssetScriptHash.Equals(AssetScriptHash) && Token.Equals(other.Token); + } + + public override bool Equals(Object other) + { + return other is Nep11BalanceKey otherKey && Equals(otherKey); + } + + public override int GetHashCode() + { + return HashCode.Combine(UserScriptHash.GetHashCode(), AssetScriptHash.GetHashCode(), Token.GetHashCode()); + } + + public void Serialize(BinaryWriter writer) + { + writer.Write(UserScriptHash); + writer.Write(AssetScriptHash); + writer.WriteVarBytes(Token.GetSpan()); + } + + public void Deserialize(ref MemoryReader reader) + { + ((ISerializable)UserScriptHash).Deserialize(ref reader); + ((ISerializable)AssetScriptHash).Deserialize(ref reader); + Token = reader.ReadVarMemory(); + } + } +} diff --git a/src/Plugins/TokensTracker/Trackers/NEP-11/Nep11Tracker.cs b/src/Plugins/TokensTracker/Trackers/NEP-11/Nep11Tracker.cs new file mode 100644 index 0000000000..12d3c208bc --- /dev/null +++ b/src/Plugins/TokensTracker/Trackers/NEP-11/Nep11Tracker.cs @@ -0,0 +1,322 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Nep11Tracker.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.Plugins.RpcServer; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.VM.Types; +using Neo.Wallets; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Array = Neo.VM.Types.Array; + +namespace Neo.Plugins.Trackers.NEP_11 +{ + class Nep11Tracker : TrackerBase + { + private const byte Nep11BalancePrefix = 0xf8; + private const byte Nep11TransferSentPrefix = 0xf9; + private const byte Nep11TransferReceivedPrefix = 0xfa; + private uint _currentHeight; + private Block _currentBlock; + private readonly HashSet _properties = new() + { + "name", + "description", + "image", + "tokenURI" + }; + + public override string TrackName => nameof(Nep11Tracker); + + public Nep11Tracker(IStore db, uint maxResult, bool shouldRecordHistory, NeoSystem system) : base(db, maxResult, shouldRecordHistory, system) + { + } + + public override void OnPersist(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) + { + _currentBlock = block; + _currentHeight = block.Index; + uint nep11TransferIndex = 0; + List transfers = new(); + foreach (Blockchain.ApplicationExecuted appExecuted in applicationExecutedList) + { + // Executions that fault won't modify storage, so we can skip them. + if (appExecuted.VMState.HasFlag(VMState.FAULT)) continue; + foreach (var notifyEventArgs in appExecuted.Notifications) + { + if (notifyEventArgs.EventName != "Transfer" || notifyEventArgs?.State is not Array stateItems || + stateItems.Count == 0) + continue; + var contract = NativeContract.ContractManagement.GetContract(snapshot, notifyEventArgs.ScriptHash); + if (contract?.Manifest.SupportedStandards.Contains("NEP-11") == true) + { + try + { + HandleNotificationNep11(notifyEventArgs.ScriptContainer, notifyEventArgs.ScriptHash, stateItems, transfers, ref nep11TransferIndex); + } + catch (Exception e) + { + Log(e.ToString(), LogLevel.Error); + throw; + } + } + + } + } + + // update nep11 balance + var contracts = new Dictionary(); + foreach (var transferRecord in transfers) + { + if (!contracts.ContainsKey(transferRecord.asset)) + { + var state = NativeContract.ContractManagement.GetContract(snapshot, transferRecord.asset); + var balanceMethod = state.Manifest.Abi.GetMethod("balanceOf", 1); + var balanceMethod2 = state.Manifest.Abi.GetMethod("balanceOf", 2); + if (balanceMethod == null && balanceMethod2 == null) + { + Log($"{state.Hash} is not nft!", LogLevel.Warning); + continue; + } + + var isDivisible = balanceMethod2 != null; + contracts[transferRecord.asset] = (isDivisible, state); + } + + var asset = contracts[transferRecord.asset]; + if (asset.isDivisible) + { + SaveDivisibleNFTBalance(transferRecord, snapshot); + } + else + { + SaveNFTBalance(transferRecord); + } + } + } + + private void SaveDivisibleNFTBalance(TransferRecord record, DataCache snapshot) + { + using ScriptBuilder sb = new(); + sb.EmitDynamicCall(record.asset, "balanceOf", record.from, record.tokenId); + sb.EmitDynamicCall(record.asset, "balanceOf", record.to, record.tokenId); + using ApplicationEngine engine = ApplicationEngine.Run(sb.ToArray(), snapshot, settings: _neoSystem.Settings, gas: 3400_0000); + if (engine.State.HasFlag(VMState.FAULT) || engine.ResultStack.Count != 2) + { + Log($"Fault: from[{record.from}] to[{record.to}] get {record.asset} token [{record.tokenId.ToHexString()}] balance fault", LogLevel.Warning); + return; + } + var toBalance = engine.ResultStack.Pop(); + var fromBalance = engine.ResultStack.Pop(); + if (toBalance is not Integer || fromBalance is not Integer) + { + Log($"Fault: from[{record.from}] to[{record.to}] get {record.asset} token [{record.tokenId.ToHexString()}] balance not number", LogLevel.Warning); + return; + } + Put(Nep11BalancePrefix, new Nep11BalanceKey(record.to, record.asset, record.tokenId), new TokenBalance { Balance = toBalance.GetInteger(), LastUpdatedBlock = _currentHeight }); + Put(Nep11BalancePrefix, new Nep11BalanceKey(record.from, record.asset, record.tokenId), new TokenBalance { Balance = fromBalance.GetInteger(), LastUpdatedBlock = _currentHeight }); + } + + private void SaveNFTBalance(TransferRecord record) + { + if (record.from != UInt160.Zero) + { + Delete(Nep11BalancePrefix, new Nep11BalanceKey(record.from, record.asset, record.tokenId)); + } + + if (record.to != UInt160.Zero) + { + Put(Nep11BalancePrefix, new Nep11BalanceKey(record.to, record.asset, record.tokenId), new TokenBalance { Balance = 1, LastUpdatedBlock = _currentHeight }); + } + } + + + private void HandleNotificationNep11(IVerifiable scriptContainer, UInt160 asset, Array stateItems, List transfers, ref uint transferIndex) + { + if (stateItems.Count != 4) return; + var transferRecord = GetTransferRecord(asset, stateItems); + if (transferRecord == null) return; + + transfers.Add(transferRecord); + if (scriptContainer is Transaction transaction) + { + RecordTransferHistoryNep11(asset, transferRecord.from, transferRecord.to, transferRecord.tokenId, transferRecord.amount, transaction.Hash, ref transferIndex); + } + } + + + private void RecordTransferHistoryNep11(UInt160 contractHash, UInt160 from, UInt160 to, ByteString tokenId, BigInteger amount, UInt256 txHash, ref uint transferIndex) + { + if (!_shouldTrackHistory) return; + if (from != UInt160.Zero) + { + Put(Nep11TransferSentPrefix, + new Nep11TransferKey(from, _currentBlock.Header.Timestamp, contractHash, tokenId, transferIndex), + new TokenTransfer + { + Amount = amount, + UserScriptHash = to, + BlockIndex = _currentHeight, + TxHash = txHash + }); + } + + if (to != UInt160.Zero) + { + Put(Nep11TransferReceivedPrefix, + new Nep11TransferKey(to, _currentBlock.Header.Timestamp, contractHash, tokenId, transferIndex), + new TokenTransfer + { + Amount = amount, + UserScriptHash = from, + BlockIndex = _currentHeight, + TxHash = txHash + }); + } + transferIndex++; + } + + + [RpcMethod] + public JToken GetNep11Transfers(JArray _params) + { + _shouldTrackHistory.True_Or(RpcError.MethodNotFound); + UInt160 userScriptHash = GetScriptHashFromParam(_params[0].AsString()); + // If start time not present, default to 1 week of history. + ulong startTime = _params.Count > 1 ? (ulong)_params[1].AsNumber() : + (DateTime.UtcNow - TimeSpan.FromDays(7)).ToTimestampMS(); + ulong endTime = _params.Count > 2 ? (ulong)_params[2].AsNumber() : DateTime.UtcNow.ToTimestampMS(); + (endTime >= startTime).True_Or(RpcError.InvalidParams); + + JObject json = new(); + json["address"] = userScriptHash.ToAddress(_neoSystem.Settings.AddressVersion); + JArray transfersSent = new(); + json["sent"] = transfersSent; + JArray transfersReceived = new(); + json["received"] = transfersReceived; + AddNep11Transfers(Nep11TransferSentPrefix, userScriptHash, startTime, endTime, transfersSent); + AddNep11Transfers(Nep11TransferReceivedPrefix, userScriptHash, startTime, endTime, transfersReceived); + return json; + } + + [RpcMethod] + public JToken GetNep11Balances(JArray _params) + { + UInt160 userScriptHash = GetScriptHashFromParam(_params[0].AsString()); + + JObject json = new(); + JArray balances = new(); + json["address"] = userScriptHash.ToAddress(_neoSystem.Settings.AddressVersion); + json["balance"] = balances; + + var map = new Dictionary>(); + int count = 0; + byte[] prefix = Key(Nep11BalancePrefix, userScriptHash); + foreach (var (key, value) in _db.FindPrefix(prefix)) + { + if (NativeContract.ContractManagement.GetContract(_neoSystem.StoreView, key.AssetScriptHash) is null) + continue; + if (!map.TryGetValue(key.AssetScriptHash, out var list)) + { + map[key.AssetScriptHash] = list = new List<(string, BigInteger, uint)>(); + } + list.Add((key.Token.GetSpan().ToHexString(), value.Balance, value.LastUpdatedBlock)); + count++; + if (count >= _maxResults) + { + break; + } + } + foreach (var key in map.Keys) + { + try + { + using var script = new ScriptBuilder(); + script.EmitDynamicCall(key, "decimals"); + script.EmitDynamicCall(key, "symbol"); + + var engine = ApplicationEngine.Run(script.ToArray(), _neoSystem.StoreView, settings: _neoSystem.Settings); + var symbol = engine.ResultStack.Pop().GetString(); + var decimals = engine.ResultStack.Pop().GetInteger(); + var name = NativeContract.ContractManagement.GetContract(_neoSystem.StoreView, key).Manifest.Name; + + balances.Add(new JObject + { + ["assethash"] = key.ToString(), + ["name"] = name, + ["symbol"] = symbol, + ["decimals"] = decimals.ToString(), + ["tokens"] = new JArray(map[key].Select(v => new JObject + { + ["tokenid"] = v.tokenid, + ["amount"] = v.amount.ToString(), + ["lastupdatedblock"] = v.height + })), + }); + } + catch { } + } + return json; + } + + [RpcMethod] + public JToken GetNep11Properties(JArray _params) + { + UInt160 nep11Hash = GetScriptHashFromParam(_params[0].AsString()); + var tokenId = _params[1].AsString().HexToBytes(); + + using ScriptBuilder sb = new(); + sb.EmitDynamicCall(nep11Hash, "properties", CallFlags.ReadOnly, tokenId); + using var snapshot = _neoSystem.GetSnapshot(); + + using var engine = ApplicationEngine.Run(sb.ToArray(), snapshot, settings: _neoSystem.Settings); + JObject json = new(); + + if (engine.State == VMState.HALT) + { + var map = engine.ResultStack.Pop(); + foreach (var keyValue in map) + { + if (keyValue.Value is CompoundType) continue; + var key = keyValue.Key.GetString(); + if (_properties.Contains(key)) + { + json[key] = keyValue.Value.GetString(); + } + else + { + json[key] = keyValue.Value.IsNull ? null : keyValue.Value.GetSpan().ToBase64(); + } + } + } + return json; + } + + private void AddNep11Transfers(byte dbPrefix, UInt160 userScriptHash, ulong startTime, ulong endTime, JArray parentJArray) + { + var transferPairs = QueryTransfers(dbPrefix, userScriptHash, startTime, endTime).Take((int)_maxResults).ToList(); + foreach (var (key, value) in transferPairs.OrderByDescending(l => l.key.TimestampMS)) + { + JObject transfer = ToJson(key, value); + transfer["tokenid"] = key.Token.GetSpan().ToHexString(); + parentJArray.Add(transfer); + } + } + } +} diff --git a/src/Plugins/TokensTracker/Trackers/NEP-11/Nep11TransferKey.cs b/src/Plugins/TokensTracker/Trackers/NEP-11/Nep11TransferKey.cs new file mode 100644 index 0000000000..5c999e8e00 --- /dev/null +++ b/src/Plugins/TokensTracker/Trackers/NEP-11/Nep11TransferKey.cs @@ -0,0 +1,80 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Nep11TransferKey.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using Neo.VM.Types; +using System; +using System.IO; + +namespace Neo.Plugins.Trackers.NEP_11 +{ + public class Nep11TransferKey : TokenTransferKey, IComparable, IEquatable + { + public ByteString Token; + public override int Size => base.Size + Token.GetVarSize(); + + public Nep11TransferKey() : this(new UInt160(), 0, new UInt160(), ByteString.Empty, 0) + { + } + + public Nep11TransferKey(UInt160 userScriptHash, ulong timestamp, UInt160 assetScriptHash, ByteString tokenId, uint xferIndex) : base(userScriptHash, timestamp, assetScriptHash, xferIndex) + { + Token = tokenId; + } + + public int CompareTo(Nep11TransferKey other) + { + if (other is null) return 1; + if (ReferenceEquals(this, other)) return 0; + int result = UserScriptHash.CompareTo(other.UserScriptHash); + if (result != 0) return result; + int result2 = TimestampMS.CompareTo(other.TimestampMS); + if (result2 != 0) return result2; + int result3 = AssetScriptHash.CompareTo(other.AssetScriptHash); + if (result3 != 0) return result3; + var result4 = BlockXferNotificationIndex.CompareTo(other.BlockXferNotificationIndex); + if (result4 != 0) return result4; + return (Token.GetInteger() - other.Token.GetInteger()).Sign; + } + + public bool Equals(Nep11TransferKey other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return UserScriptHash.Equals(other.UserScriptHash) + && TimestampMS.Equals(other.TimestampMS) && AssetScriptHash.Equals(other.AssetScriptHash) + && Token.Equals(other.Token) + && BlockXferNotificationIndex.Equals(other.BlockXferNotificationIndex); + } + + public override bool Equals(Object other) + { + return other is Nep11TransferKey otherKey && Equals(otherKey); + } + + public override int GetHashCode() + { + return HashCode.Combine(UserScriptHash.GetHashCode(), TimestampMS.GetHashCode(), AssetScriptHash.GetHashCode(), BlockXferNotificationIndex.GetHashCode(), Token.GetHashCode()); + } + + public override void Serialize(BinaryWriter writer) + { + base.Serialize(writer); + writer.WriteVarBytes(Token.GetSpan()); + } + + public override void Deserialize(ref MemoryReader reader) + { + base.Deserialize(ref reader); + Token = reader.ReadVarMemory(); + } + } +} diff --git a/src/Plugins/TokensTracker/Trackers/NEP-17/Nep17BalanceKey.cs b/src/Plugins/TokensTracker/Trackers/NEP-17/Nep17BalanceKey.cs new file mode 100644 index 0000000000..bbceabec2b --- /dev/null +++ b/src/Plugins/TokensTracker/Trackers/NEP-17/Nep17BalanceKey.cs @@ -0,0 +1,75 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Nep17BalanceKey.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using System; +using System.IO; + +namespace Neo.Plugins.Trackers.NEP_17 +{ + public class Nep17BalanceKey : IComparable, IEquatable, ISerializable + { + public readonly UInt160 UserScriptHash; + public readonly UInt160 AssetScriptHash; + + public int Size => UInt160.Length + UInt160.Length; + + public Nep17BalanceKey() : this(new UInt160(), new UInt160()) + { + } + + public Nep17BalanceKey(UInt160 userScriptHash, UInt160 assetScriptHash) + { + if (userScriptHash == null || assetScriptHash == null) + throw new ArgumentNullException(); + UserScriptHash = userScriptHash; + AssetScriptHash = assetScriptHash; + } + + public int CompareTo(Nep17BalanceKey other) + { + if (other is null) return 1; + if (ReferenceEquals(this, other)) return 0; + int result = UserScriptHash.CompareTo(other.UserScriptHash); + if (result != 0) return result; + return AssetScriptHash.CompareTo(other.AssetScriptHash); + } + + public bool Equals(Nep17BalanceKey other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return UserScriptHash.Equals(other.UserScriptHash) && AssetScriptHash.Equals(AssetScriptHash); + } + + public override bool Equals(Object other) + { + return other is Nep17BalanceKey otherKey && Equals(otherKey); + } + + public override int GetHashCode() + { + return HashCode.Combine(UserScriptHash.GetHashCode(), AssetScriptHash.GetHashCode()); + } + + public void Serialize(BinaryWriter writer) + { + writer.Write(UserScriptHash); + writer.Write(AssetScriptHash); + } + + public void Deserialize(ref MemoryReader reader) + { + ((ISerializable)UserScriptHash).Deserialize(ref reader); + ((ISerializable)AssetScriptHash).Deserialize(ref reader); + } + } +} diff --git a/src/Plugins/TokensTracker/Trackers/NEP-17/Nep17Tracker.cs b/src/Plugins/TokensTracker/Trackers/NEP-17/Nep17Tracker.cs new file mode 100644 index 0000000000..d4698cba1e --- /dev/null +++ b/src/Plugins/TokensTracker/Trackers/NEP-17/Nep17Tracker.cs @@ -0,0 +1,257 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Nep17Tracker.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.Plugins.RpcServer; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.VM.Types; +using Neo.Wallets; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Array = Neo.VM.Types.Array; + +namespace Neo.Plugins.Trackers.NEP_17 +{ + record BalanceChangeRecord(UInt160 User, UInt160 Asset); + + class Nep17Tracker : TrackerBase + { + private const byte Nep17BalancePrefix = 0xe8; + private const byte Nep17TransferSentPrefix = 0xe9; + private const byte Nep17TransferReceivedPrefix = 0xea; + private uint _currentHeight; + private Block _currentBlock; + + public override string TrackName => nameof(Nep17Tracker); + + public Nep17Tracker(IStore db, uint maxResult, bool shouldRecordHistory, NeoSystem system) : base(db, maxResult, shouldRecordHistory, system) + { + } + + public override void OnPersist(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) + { + _currentBlock = block; + _currentHeight = block.Index; + uint nep17TransferIndex = 0; + var balanceChangeRecords = new HashSet(); + + foreach (Blockchain.ApplicationExecuted appExecuted in applicationExecutedList) + { + // Executions that fault won't modify storage, so we can skip them. + if (appExecuted.VMState.HasFlag(VMState.FAULT)) continue; + foreach (var notifyEventArgs in appExecuted.Notifications) + { + if (notifyEventArgs.EventName != "Transfer" || notifyEventArgs?.State is not Array stateItems || stateItems.Count == 0) + continue; + var contract = NativeContract.ContractManagement.GetContract(snapshot, notifyEventArgs.ScriptHash); + if (contract?.Manifest.SupportedStandards.Contains("NEP-17") == true) + { + try + { + HandleNotificationNep17(notifyEventArgs.ScriptContainer, notifyEventArgs.ScriptHash, stateItems, balanceChangeRecords, ref nep17TransferIndex); + } + catch (Exception e) + { + Log(e.ToString(), LogLevel.Error); + throw; + } + } + } + } + + //update nep17 balance + foreach (var balanceChangeRecord in balanceChangeRecords) + { + try + { + SaveNep17Balance(balanceChangeRecord, snapshot); + } + catch (Exception e) + { + Log(e.ToString(), LogLevel.Error); + throw; + } + } + } + + + private void HandleNotificationNep17(IVerifiable scriptContainer, UInt160 asset, Array stateItems, HashSet balanceChangeRecords, ref uint transferIndex) + { + if (stateItems.Count != 3) return; + var transferRecord = GetTransferRecord(asset, stateItems); + if (transferRecord == null) return; + if (transferRecord.from != UInt160.Zero) + { + balanceChangeRecords.Add(new BalanceChangeRecord(transferRecord.from, asset)); + } + if (transferRecord.to != UInt160.Zero) + { + balanceChangeRecords.Add(new BalanceChangeRecord(transferRecord.to, asset)); + } + if (scriptContainer is Transaction transaction) + { + RecordTransferHistoryNep17(asset, transferRecord.from, transferRecord.to, transferRecord.amount, transaction.Hash, ref transferIndex); + } + } + + + private void SaveNep17Balance(BalanceChangeRecord balanceChanged, DataCache snapshot) + { + var key = new Nep17BalanceKey(balanceChanged.User, balanceChanged.Asset); + using ScriptBuilder sb = new(); + sb.EmitDynamicCall(balanceChanged.Asset, "balanceOf", balanceChanged.User); + using ApplicationEngine engine = ApplicationEngine.Run(sb.ToArray(), snapshot, settings: _neoSystem.Settings, gas: 1700_0000); + + if (engine.State.HasFlag(VMState.FAULT) || engine.ResultStack.Count == 0) + { + Log($"Fault:{balanceChanged.User} get {balanceChanged.Asset} balance fault", LogLevel.Warning); + return; + } + + var balanceItem = engine.ResultStack.Pop(); + if (balanceItem is not Integer) + { + Log($"Fault:{balanceChanged.User} get {balanceChanged.Asset} balance not number", LogLevel.Warning); + return; + } + + var balance = balanceItem.GetInteger(); + + if (balance.IsZero) + { + Delete(Nep17BalancePrefix, key); + return; + } + + Put(Nep17BalancePrefix, key, new TokenBalance { Balance = balance, LastUpdatedBlock = _currentHeight }); + } + + + [RpcMethod] + public JToken GetNep17Transfers(JArray _params) + { + _shouldTrackHistory.True_Or(RpcError.MethodNotFound); + UInt160 userScriptHash = GetScriptHashFromParam(_params[0].AsString()); + // If start time not present, default to 1 week of history. + ulong startTime = _params.Count > 1 ? (ulong)_params[1].AsNumber() : + (DateTime.UtcNow - TimeSpan.FromDays(7)).ToTimestampMS(); + ulong endTime = _params.Count > 2 ? (ulong)_params[2].AsNumber() : DateTime.UtcNow.ToTimestampMS(); + + (endTime >= startTime).True_Or(RpcError.InvalidParams); + + JObject json = new(); + json["address"] = userScriptHash.ToAddress(_neoSystem.Settings.AddressVersion); + JArray transfersSent = new(); + json["sent"] = transfersSent; + JArray transfersReceived = new(); + json["received"] = transfersReceived; + AddNep17Transfers(Nep17TransferSentPrefix, userScriptHash, startTime, endTime, transfersSent); + AddNep17Transfers(Nep17TransferReceivedPrefix, userScriptHash, startTime, endTime, transfersReceived); + return json; + } + + [RpcMethod] + public JToken GetNep17Balances(JArray _params) + { + UInt160 userScriptHash = GetScriptHashFromParam(_params[0].AsString()); + + JObject json = new(); + JArray balances = new(); + json["address"] = userScriptHash.ToAddress(_neoSystem.Settings.AddressVersion); + json["balance"] = balances; + + int count = 0; + byte[] prefix = Key(Nep17BalancePrefix, userScriptHash); + foreach (var (key, value) in _db.FindPrefix(prefix)) + { + if (NativeContract.ContractManagement.GetContract(_neoSystem.StoreView, key.AssetScriptHash) is null) + continue; + + try + { + using var script = new ScriptBuilder(); + script.EmitDynamicCall(key.AssetScriptHash, "decimals"); + script.EmitDynamicCall(key.AssetScriptHash, "symbol"); + + var engine = ApplicationEngine.Run(script.ToArray(), _neoSystem.StoreView, settings: _neoSystem.Settings); + var symbol = engine.ResultStack.Pop().GetString(); + var decimals = engine.ResultStack.Pop().GetInteger(); + var name = NativeContract.ContractManagement.GetContract(_neoSystem.StoreView, key.AssetScriptHash).Manifest.Name; + + balances.Add(new JObject + { + ["assethash"] = key.AssetScriptHash.ToString(), + ["name"] = name, + ["symbol"] = symbol, + ["decimals"] = decimals.ToString(), + ["amount"] = value.Balance.ToString(), + ["lastupdatedblock"] = value.LastUpdatedBlock + }); + count++; + if (count >= _maxResults) + { + break; + } + } + catch { } + } + return json; + } + + private void AddNep17Transfers(byte dbPrefix, UInt160 userScriptHash, ulong startTime, ulong endTime, JArray parentJArray) + { + var transferPairs = QueryTransfers(dbPrefix, userScriptHash, startTime, endTime).Take((int)_maxResults).ToList(); + foreach (var (key, value) in transferPairs.OrderByDescending(l => l.key.TimestampMS)) + { + parentJArray.Add(ToJson(key, value)); + } + } + + + private void RecordTransferHistoryNep17(UInt160 scriptHash, UInt160 from, UInt160 to, BigInteger amount, UInt256 txHash, ref uint transferIndex) + { + if (!_shouldTrackHistory) return; + if (from != UInt160.Zero) + { + Put(Nep17TransferSentPrefix, + new Nep17TransferKey(from, _currentBlock.Header.Timestamp, scriptHash, transferIndex), + new TokenTransfer + { + Amount = amount, + UserScriptHash = to, + BlockIndex = _currentHeight, + TxHash = txHash + }); + } + + if (to != UInt160.Zero) + { + Put(Nep17TransferReceivedPrefix, + new Nep17TransferKey(to, _currentBlock.Header.Timestamp, scriptHash, transferIndex), + new TokenTransfer + { + Amount = amount, + UserScriptHash = from, + BlockIndex = _currentHeight, + TxHash = txHash + }); + } + transferIndex++; + } + } +} diff --git a/src/Plugins/TokensTracker/Trackers/NEP-17/Nep17TransferKey.cs b/src/Plugins/TokensTracker/Trackers/NEP-17/Nep17TransferKey.cs new file mode 100644 index 0000000000..8f48195fc9 --- /dev/null +++ b/src/Plugins/TokensTracker/Trackers/NEP-17/Nep17TransferKey.cs @@ -0,0 +1,59 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Nep17TransferKey.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using System; + +namespace Neo.Plugins.Trackers.NEP_17 +{ + public class Nep17TransferKey : TokenTransferKey, IComparable, IEquatable, ISerializable + { + public Nep17TransferKey() : base(new UInt160(), 0, new UInt160(), 0) + { + } + + public Nep17TransferKey(UInt160 userScriptHash, ulong timestamp, UInt160 assetScriptHash, uint xferIndex) : base(userScriptHash, timestamp, assetScriptHash, xferIndex) + { + } + + public int CompareTo(Nep17TransferKey other) + { + if (other is null) return 1; + if (ReferenceEquals(this, other)) return 0; + int result = UserScriptHash.CompareTo(other.UserScriptHash); + if (result != 0) return result; + int result2 = TimestampMS.CompareTo(other.TimestampMS); + if (result2 != 0) return result2; + int result3 = AssetScriptHash.CompareTo(other.AssetScriptHash); + if (result3 != 0) return result3; + return BlockXferNotificationIndex.CompareTo(other.BlockXferNotificationIndex); + } + + public bool Equals(Nep17TransferKey other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return UserScriptHash.Equals(other.UserScriptHash) + && TimestampMS.Equals(other.TimestampMS) && AssetScriptHash.Equals(other.AssetScriptHash) + && BlockXferNotificationIndex.Equals(other.BlockXferNotificationIndex); + } + + public override bool Equals(Object other) + { + return other is Nep17TransferKey otherKey && Equals(otherKey); + } + + public override int GetHashCode() + { + return HashCode.Combine(UserScriptHash.GetHashCode(), TimestampMS.GetHashCode(), AssetScriptHash.GetHashCode(), BlockXferNotificationIndex.GetHashCode()); + } + } +} diff --git a/src/Plugins/TokensTracker/Trackers/TokenBalance.cs b/src/Plugins/TokensTracker/Trackers/TokenBalance.cs new file mode 100644 index 0000000000..f54a7c9856 --- /dev/null +++ b/src/Plugins/TokensTracker/Trackers/TokenBalance.cs @@ -0,0 +1,39 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TokenBalance.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using System.IO; +using System.Numerics; + +namespace Neo.Plugins.Trackers +{ + public class TokenBalance : ISerializable + { + public BigInteger Balance; + public uint LastUpdatedBlock; + + int ISerializable.Size => + Balance.GetVarSize() + // Balance + sizeof(uint); // LastUpdatedBlock + + void ISerializable.Serialize(BinaryWriter writer) + { + writer.WriteVarBytes(Balance.ToByteArray()); + writer.Write(LastUpdatedBlock); + } + + void ISerializable.Deserialize(ref MemoryReader reader) + { + Balance = new BigInteger(reader.ReadVarMemory(32).Span); + LastUpdatedBlock = reader.ReadUInt32(); + } + } +} diff --git a/src/Plugins/TokensTracker/Trackers/TokenTransfer.cs b/src/Plugins/TokensTracker/Trackers/TokenTransfer.cs new file mode 100644 index 0000000000..0a221850fc --- /dev/null +++ b/src/Plugins/TokensTracker/Trackers/TokenTransfer.cs @@ -0,0 +1,47 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TokenTransfer.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using System.IO; +using System.Numerics; + +namespace Neo.Plugins.Trackers +{ + public class TokenTransfer : ISerializable + { + public UInt160 UserScriptHash; + public uint BlockIndex; + public UInt256 TxHash; + public BigInteger Amount; + + int ISerializable.Size => + UInt160.Length + // UserScriptHash + sizeof(uint) + // BlockIndex + UInt256.Length + // TxHash + Amount.GetVarSize(); // Amount + + void ISerializable.Serialize(BinaryWriter writer) + { + writer.Write(UserScriptHash); + writer.Write(BlockIndex); + writer.Write(TxHash); + writer.WriteVarBytes(Amount.ToByteArray()); + } + + void ISerializable.Deserialize(ref MemoryReader reader) + { + UserScriptHash = reader.ReadSerializable(); + BlockIndex = reader.ReadUInt32(); + TxHash = reader.ReadSerializable(); + Amount = new BigInteger(reader.ReadVarMemory(32).Span); + } + } +} diff --git a/src/Plugins/TokensTracker/Trackers/TokenTransferKey.cs b/src/Plugins/TokensTracker/Trackers/TokenTransferKey.cs new file mode 100644 index 0000000000..252eb20af0 --- /dev/null +++ b/src/Plugins/TokensTracker/Trackers/TokenTransferKey.cs @@ -0,0 +1,57 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TokenTransferKey.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using System; +using System.Buffers.Binary; +using System.IO; + +namespace Neo.Plugins.Trackers +{ + public class TokenTransferKey : ISerializable + { + public UInt160 UserScriptHash { get; protected set; } + public ulong TimestampMS { get; protected set; } + public UInt160 AssetScriptHash { get; protected set; } + public uint BlockXferNotificationIndex { get; protected set; } + + public TokenTransferKey(UInt160 userScriptHash, ulong timestamp, UInt160 assetScriptHash, uint xferIndex) + { + if (userScriptHash is null || assetScriptHash is null) + throw new ArgumentNullException(); + UserScriptHash = userScriptHash; + TimestampMS = timestamp; + AssetScriptHash = assetScriptHash; + BlockXferNotificationIndex = xferIndex; + } + public virtual void Serialize(BinaryWriter writer) + { + writer.Write(UserScriptHash); + writer.Write(BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(TimestampMS) : TimestampMS); + writer.Write(AssetScriptHash); + writer.Write(BlockXferNotificationIndex); + } + + public virtual void Deserialize(ref MemoryReader reader) + { + UserScriptHash.Deserialize(ref reader); + TimestampMS = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(reader.ReadUInt64()) : reader.ReadUInt64(); + AssetScriptHash.Deserialize(ref reader); + BlockXferNotificationIndex = reader.ReadUInt32(); + } + + public virtual int Size => + UInt160.Length + //UserScriptHash + sizeof(ulong) + //TimestampMS + UInt160.Length + //AssetScriptHash + sizeof(uint); //BlockXferNotificationIndex + } +} diff --git a/src/Plugins/TokensTracker/Trackers/TrackerBase.cs b/src/Plugins/TokensTracker/Trackers/TrackerBase.cs new file mode 100644 index 0000000000..471362b019 --- /dev/null +++ b/src/Plugins/TokensTracker/Trackers/TrackerBase.cs @@ -0,0 +1,162 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TrackerBase.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using Neo.Json; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.VM.Types; +using Neo.Wallets; +using System; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Numerics; +using Array = Neo.VM.Types.Array; + +namespace Neo.Plugins.Trackers +{ + record TransferRecord(UInt160 asset, UInt160 from, UInt160 to, byte[] tokenId, BigInteger amount); + + abstract class TrackerBase + { + protected bool _shouldTrackHistory; + protected uint _maxResults; + protected IStore _db; + private ISnapshot _levelDbSnapshot; + protected NeoSystem _neoSystem; + public abstract string TrackName { get; } + + protected TrackerBase(IStore db, uint maxResult, bool shouldTrackHistory, NeoSystem neoSystem) + { + _db = db; + _maxResults = maxResult; + _shouldTrackHistory = shouldTrackHistory; + _neoSystem = neoSystem; + } + + public abstract void OnPersist(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList); + + public void ResetBatch() + { + _levelDbSnapshot?.Dispose(); + _levelDbSnapshot = _db.GetSnapshot(); + } + + public void Commit() + { + _levelDbSnapshot?.Commit(); + } + + public IEnumerable<(TKey key, TValue val)> QueryTransfers(byte dbPrefix, UInt160 userScriptHash, ulong startTime, ulong endTime) + where TKey : ISerializable, new() + where TValue : class, ISerializable, new() + { + var prefix = new[] { dbPrefix }.Concat(userScriptHash.ToArray()).ToArray(); + byte[] startTimeBytes, endTimeBytes; + if (BitConverter.IsLittleEndian) + { + startTimeBytes = BitConverter.GetBytes(BinaryPrimitives.ReverseEndianness(startTime)); + endTimeBytes = BitConverter.GetBytes(BinaryPrimitives.ReverseEndianness(endTime)); + } + else + { + startTimeBytes = BitConverter.GetBytes(startTime); + endTimeBytes = BitConverter.GetBytes(endTime); + } + var transferPairs = _db.FindRange(prefix.Concat(startTimeBytes).ToArray(), prefix.Concat(endTimeBytes).ToArray()); + return transferPairs; + } + + protected static byte[] Key(byte prefix, ISerializable key) + { + byte[] buffer = new byte[key.Size + 1]; + using (MemoryStream ms = new(buffer, true)) + using (BinaryWriter writer = new(ms)) + { + writer.Write(prefix); + key.Serialize(writer); + } + return buffer; + } + + protected void Put(byte prefix, ISerializable key, ISerializable value) + { + _levelDbSnapshot.Put(Key(prefix, key), value.ToArray()); + } + + protected void Delete(byte prefix, ISerializable key) + { + _levelDbSnapshot.Delete(Key(prefix, key)); + } + + protected TransferRecord GetTransferRecord(UInt160 asset, Array stateItems) + { + if (stateItems.Count < 3) + { + return null; + } + var fromItem = stateItems[0]; + var toItem = stateItems[1]; + var amountItem = stateItems[2]; + if (fromItem.NotNull() && fromItem is not ByteString) + return null; + if (toItem.NotNull() && toItem is not ByteString) + return null; + if (amountItem is not ByteString && amountItem is not Integer) + return null; + + byte[] fromBytes = fromItem.IsNull ? null : fromItem.GetSpan().ToArray(); + if (fromBytes != null && fromBytes.Length != UInt160.Length) + return null; + byte[] toBytes = toItem.IsNull ? null : toItem.GetSpan().ToArray(); + if (toBytes != null && toBytes.Length != UInt160.Length) + return null; + if (fromBytes == null && toBytes == null) + return null; + + var from = fromBytes == null ? UInt160.Zero : new UInt160(fromBytes); + var to = toBytes == null ? UInt160.Zero : new UInt160(toBytes); + return stateItems.Count switch + { + 3 => new TransferRecord(asset, @from, to, null, amountItem.GetInteger()), + 4 when (stateItems[3] is ByteString tokenId) => new TransferRecord(asset, @from, to, tokenId.Memory.ToArray(), amountItem.GetInteger()), + _ => null + }; + } + + protected JObject ToJson(TokenTransferKey key, TokenTransfer value) + { + JObject transfer = new(); + transfer["timestamp"] = key.TimestampMS; + transfer["assethash"] = key.AssetScriptHash.ToString(); + transfer["transferaddress"] = value.UserScriptHash == UInt160.Zero ? null : value.UserScriptHash.ToAddress(_neoSystem.Settings.AddressVersion); + transfer["amount"] = value.Amount.ToString(); + transfer["blockindex"] = value.BlockIndex; + transfer["transfernotifyindex"] = key.BlockXferNotificationIndex; + transfer["txhash"] = value.TxHash.ToString(); + return transfer; + } + + public UInt160 GetScriptHashFromParam(string addressOrScriptHash) + { + return addressOrScriptHash.Length < 40 ? + addressOrScriptHash.ToScriptHash(_neoSystem.Settings.AddressVersion) : UInt160.Parse(addressOrScriptHash); + } + + public void Log(string message, LogLevel level = LogLevel.Info) + { + Utility.Log(TrackName, level, message); + } + } +} diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index 875c10b42b..a2fc251365 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -1,17 +1,26 @@ - + - net7.0 + 12.0 + false false + true + 0 + true + TestResults/ + lcov - - - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - diff --git a/tests/Neo.ConsoleService.Tests/CommandTokenTest.cs b/tests/Neo.ConsoleService.Tests/CommandTokenTest.cs new file mode 100644 index 0000000000..b5f43f9d04 --- /dev/null +++ b/tests/Neo.ConsoleService.Tests/CommandTokenTest.cs @@ -0,0 +1,103 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// CommandTokenTest.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Linq; + +namespace Neo.ConsoleService.Tests +{ + [TestClass] + public class CommandTokenTest + { + [TestMethod] + public void Test1() + { + var cmd = " "; + var args = CommandToken.Parse(cmd).ToArray(); + + AreEqual(args, new CommandSpaceToken(0, 1)); + Assert.AreEqual(cmd, CommandToken.ToString(args)); + } + + [TestMethod] + public void Test2() + { + var cmd = "show state"; + var args = CommandToken.Parse(cmd).ToArray(); + + AreEqual(args, new CommandStringToken(0, "show"), new CommandSpaceToken(4, 2), new CommandStringToken(6, "state")); + Assert.AreEqual(cmd, CommandToken.ToString(args)); + } + + [TestMethod] + public void Test3() + { + var cmd = "show \"hello world\""; + var args = CommandToken.Parse(cmd).ToArray(); + + AreEqual(args, + new CommandStringToken(0, "show"), + new CommandSpaceToken(4, 1), + new CommandQuoteToken(5, '"'), + new CommandStringToken(6, "hello world"), + new CommandQuoteToken(17, '"') + ); + Assert.AreEqual(cmd, CommandToken.ToString(args)); + } + + [TestMethod] + public void Test4() + { + var cmd = "show \"'\""; + var args = CommandToken.Parse(cmd).ToArray(); + + AreEqual(args, + new CommandStringToken(0, "show"), + new CommandSpaceToken(4, 1), + new CommandQuoteToken(5, '"'), + new CommandStringToken(6, "'"), + new CommandQuoteToken(7, '"') + ); + Assert.AreEqual(cmd, CommandToken.ToString(args)); + } + + [TestMethod] + public void Test5() + { + var cmd = "show \"123\\\"456\""; + var args = CommandToken.Parse(cmd).ToArray(); + + AreEqual(args, + new CommandStringToken(0, "show"), + new CommandSpaceToken(4, 1), + new CommandQuoteToken(5, '"'), + new CommandStringToken(6, "123\\\"456"), + new CommandQuoteToken(14, '"') + ); + Assert.AreEqual(cmd, CommandToken.ToString(args)); + } + + private void AreEqual(CommandToken[] args, params CommandToken[] compare) + { + Assert.AreEqual(compare.Length, args.Length); + + for (int x = 0; x < args.Length; x++) + { + var a = args[x]; + var b = compare[x]; + + Assert.AreEqual(a.Type, b.Type); + Assert.AreEqual(a.Value, b.Value); + Assert.AreEqual(a.Offset, b.Offset); + } + } + } +} diff --git a/tests/Neo.ConsoleService.Tests/Neo.ConsoleService.Tests.csproj b/tests/Neo.ConsoleService.Tests/Neo.ConsoleService.Tests.csproj new file mode 100644 index 0000000000..6562480859 --- /dev/null +++ b/tests/Neo.ConsoleService.Tests/Neo.ConsoleService.Tests.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + neo_cli.Tests + + + + + + + + + + + + + diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/Neo.Cryptography.BLS12_381.Tests.csproj b/tests/Neo.Cryptography.BLS12_381.Tests/Neo.Cryptography.BLS12_381.Tests.csproj new file mode 100644 index 0000000000..999bc1c5f4 --- /dev/null +++ b/tests/Neo.Cryptography.BLS12_381.Tests/Neo.Cryptography.BLS12_381.Tests.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp.cs b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp.cs new file mode 100644 index 0000000000..bc9cd480f2 --- /dev/null +++ b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp.cs @@ -0,0 +1,353 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Fp.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Collections; +using System.Runtime.InteropServices; +using static Neo.Cryptography.BLS12_381.ConstantTimeUtility; + +namespace Neo.Cryptography.BLS12_381.Tests; + +[TestClass] +public class UT_Fp +{ + [TestMethod] + public void TestSize() + { + Assert.AreEqual(Fp.Size, Marshal.SizeOf()); + } + + [TestMethod] + public void TestEquality() + { + static bool IsEqual(in Fp a, in Fp b) + { + bool eq = StructuralComparisons.StructuralEqualityComparer.Equals(a, b); + bool ct_eq = a == b; + Assert.AreEqual(eq, ct_eq); + return eq; + } + + Assert.IsTrue(IsEqual(Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }))); + + Assert.IsFalse(IsEqual(Fp.FromRawUnchecked(new ulong[] { 7, 2, 3, 4, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }))); + Assert.IsFalse(IsEqual(Fp.FromRawUnchecked(new ulong[] { 1, 7, 3, 4, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }))); + Assert.IsFalse(IsEqual(Fp.FromRawUnchecked(new ulong[] { 1, 2, 7, 4, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }))); + Assert.IsFalse(IsEqual(Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 7, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }))); + Assert.IsFalse(IsEqual(Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 7, 6 }), Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }))); + Assert.IsFalse(IsEqual(Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 7 }), Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }))); + } + + [TestMethod] + public void TestConditionalSelection() + { + Fp a = Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }); + Fp b = Fp.FromRawUnchecked(new ulong[] { 7, 8, 9, 10, 11, 12 }); + + Assert.AreEqual(a, ConditionalSelect(in a, in b, false)); + Assert.AreEqual(b, ConditionalSelect(in a, in b, true)); + } + + [TestMethod] + public void TestSquaring() + { + Fp a = Fp.FromRawUnchecked(new ulong[] + { + 0xd215_d276_8e83_191b, + 0x5085_d80f_8fb2_8261, + 0xce9a_032d_df39_3a56, + 0x3e9c_4fff_2ca0_c4bb, + 0x6436_b6f7_f4d9_5dfb, + 0x1060_6628_ad4a_4d90 + }); + Fp b = Fp.FromRawUnchecked(new ulong[] + { + 0x33d9_c42a_3cb3_e235, + 0xdad1_1a09_4c4c_d455, + 0xa2f1_44bd_729a_aeba, + 0xd415_0932_be9f_feac, + 0xe27b_c7c4_7d44_ee50, + 0x14b6_a78d_3ec7_a560 + }); + + Assert.AreEqual(b, a.Square()); + } + + [TestMethod] + public void TestMultiplication() + { + Fp a = Fp.FromRawUnchecked(new ulong[] + { + 0x0397_a383_2017_0cd4, + 0x734c_1b2c_9e76_1d30, + 0x5ed2_55ad_9a48_beb5, + 0x095a_3c6b_22a7_fcfc, + 0x2294_ce75_d4e2_6a27, + 0x1333_8bd8_7001_1ebb + }); + Fp b = Fp.FromRawUnchecked(new ulong[] + { + 0xb9c3_c7c5_b119_6af7, + 0x2580_e208_6ce3_35c1, + 0xf49a_ed3d_8a57_ef42, + 0x41f2_81e4_9846_e878, + 0xe076_2346_c384_52ce, + 0x0652_e893_26e5_7dc0 + }); + Fp c = Fp.FromRawUnchecked(new ulong[] + { + 0xf96e_f3d7_11ab_5355, + 0xe8d4_59ea_00f1_48dd, + 0x53f7_354a_5f00_fa78, + 0x9e34_a4f3_125c_5f83, + 0x3fbe_0c47_ca74_c19e, + 0x01b0_6a8b_bd4a_dfe4 + }); + + Assert.AreEqual(c, a * b); + } + + [TestMethod] + public void TestAddition() + { + Fp a = Fp.FromRawUnchecked(new ulong[] + { + 0x5360_bb59_7867_8032, + 0x7dd2_75ae_799e_128e, + 0x5c5b_5071_ce4f_4dcf, + 0xcdb2_1f93_078d_bb3e, + 0xc323_65c5_e73f_474a, + 0x115a_2a54_89ba_be5b + }); + Fp b = Fp.FromRawUnchecked(new ulong[] + { + 0x9fd2_8773_3d23_dda0, + 0xb16b_f2af_738b_3554, + 0x3e57_a75b_d3cc_6d1d, + 0x900b_c0bd_627f_d6d6, + 0xd319_a080_efb2_45fe, + 0x15fd_caa4_e4bb_2091 + }); + Fp c = Fp.FromRawUnchecked(new ulong[] + { + 0x3934_42cc_b58b_b327, + 0x1092_685f_3bd5_47e3, + 0x3382_252c_ab6a_c4c9, + 0xf946_94cb_7688_7f55, + 0x4b21_5e90_93a5_e071, + 0x0d56_e30f_34f5_f853 + }); + + Assert.AreEqual(c, a + b); + } + + [TestMethod] + public void TestSubtraction() + { + Fp a = Fp.FromRawUnchecked(new ulong[] + { + 0x5360_bb59_7867_8032, + 0x7dd2_75ae_799e_128e, + 0x5c5b_5071_ce4f_4dcf, + 0xcdb2_1f93_078d_bb3e, + 0xc323_65c5_e73f_474a, + 0x115a_2a54_89ba_be5b + }); + Fp b = Fp.FromRawUnchecked(new ulong[] + { + 0x9fd2_8773_3d23_dda0, + 0xb16b_f2af_738b_3554, + 0x3e57_a75b_d3cc_6d1d, + 0x900b_c0bd_627f_d6d6, + 0xd319_a080_efb2_45fe, + 0x15fd_caa4_e4bb_2091 + }); + Fp c = Fp.FromRawUnchecked(new ulong[] + { + 0x6d8d_33e6_3b43_4d3d, + 0xeb12_82fd_b766_dd39, + 0x8534_7bb6_f133_d6d5, + 0xa21d_aa5a_9892_f727, + 0x3b25_6cfb_3ad8_ae23, + 0x155d_7199_de7f_8464 + }); + + Assert.AreEqual(c, a - b); + } + + [TestMethod] + public void TestNegation() + { + Fp a = Fp.FromRawUnchecked(new ulong[] + { + 0x5360_bb59_7867_8032, + 0x7dd2_75ae_799e_128e, + 0x5c5b_5071_ce4f_4dcf, + 0xcdb2_1f93_078d_bb3e, + 0xc323_65c5_e73f_474a, + 0x115a_2a54_89ba_be5b + }); + Fp b = Fp.FromRawUnchecked(new ulong[] + { + 0x669e_44a6_8798_2a79, + 0xa0d9_8a50_37b5_ed71, + 0x0ad5_822f_2861_a854, + 0x96c5_2bf1_ebf7_5781, + 0x87f8_41f0_5c0c_658c, + 0x08a6_e795_afc5_283e + }); + + Assert.AreEqual(b, -a); + } + + [TestMethod] + public void TestToString() + { + Fp a = Fp.FromRawUnchecked(new ulong[] + { + 0x5360_bb59_7867_8032, + 0x7dd2_75ae_799e_128e, + 0x5c5b_5071_ce4f_4dcf, + 0xcdb2_1f93_078d_bb3e, + 0xc323_65c5_e73f_474a, + 0x115a_2a54_89ba_be5b + }); + + Assert.AreEqual("0x104bf052ad3bc99bcb176c24a06a6c3aad4eaf2308fc4d282e106c84a757d061052630515305e59bdddf8111bfdeb704", a.ToString()); + } + + [TestMethod] + public void TestConstructor() + { + Fp a = Fp.FromRawUnchecked(new ulong[] + { + 0xdc90_6d9b_e3f9_5dc8, + 0x8755_caf7_4596_91a1, + 0xcff1_a7f4_e958_3ab3, + 0x9b43_821f_849e_2284, + 0xf575_54f3_a297_4f3f, + 0x085d_bea8_4ed4_7f79 + }); + + for (int i = 0; i < 100; i++) + { + a = a.Square(); + byte[] tmp = a.ToArray(); + Fp b = Fp.FromBytes(tmp); + + Assert.AreEqual(b, a); + } + + Assert.AreEqual(-Fp.One, Fp.FromBytes(new byte[] + { + 26, 1, 17, 234, 57, 127, 230, 154, 75, 27, 167, 182, 67, 75, 172, 215, 100, 119, 75, + 132, 243, 133, 18, 191, 103, 48, 210, 160, 246, 176, 246, 36, 30, 171, 255, 254, 177, + 83, 255, 255, 185, 254, 255, 255, 255, 255, 170, 170 + })); + + Assert.ThrowsException(() => Fp.FromBytes(new byte[] + { + 27, 1, 17, 234, 57, 127, 230, 154, 75, 27, 167, 182, 67, 75, 172, 215, 100, 119, 75, + 132, 243, 133, 18, 191, 103, 48, 210, 160, 246, 176, 246, 36, 30, 171, 255, 254, 177, + 83, 255, 255, 185, 254, 255, 255, 255, 255, 170, 170 + })); + + Assert.ThrowsException(() => Fp.FromBytes(Enumerable.Repeat(0xff, 48).ToArray())); + } + + [TestMethod] + public void TestSqrt() + { + // a = 4 + Fp a = Fp.FromRawUnchecked(new ulong[] + { + 0xaa27_0000_000c_fff3, + 0x53cc_0032_fc34_000a, + 0x478f_e97a_6b0a_807f, + 0xb1d3_7ebe_e6ba_24d7, + 0x8ec9_733b_bf78_ab2f, + 0x09d6_4551_3d83_de7e + }); + + // b = 2 + Fp b = Fp.FromRawUnchecked(new ulong[] + { + 0x3213_0000_0006_554f, + 0xb93c_0018_d6c4_0005, + 0x5760_5e0d_b0dd_bb51, + 0x8b25_6521_ed1f_9bcb, + 0x6cf2_8d79_0162_2c03, + 0x11eb_ab9d_bb81_e28c + }); + + // sqrt(4) = -2 + Assert.AreEqual(b, -a.Sqrt()); + } + + [TestMethod] + public void TestInversion() + { + Fp a = Fp.FromRawUnchecked(new ulong[] + { + 0x43b4_3a50_78ac_2076, + 0x1ce0_7630_46f8_962b, + 0x724a_5276_486d_735c, + 0x6f05_c2a6_282d_48fd, + 0x2095_bd5b_b4ca_9331, + 0x03b3_5b38_94b0_f7da + }); + Fp b = Fp.FromRawUnchecked(new ulong[] + { + 0x69ec_d704_0952_148f, + 0x985c_cc20_2219_0f55, + 0xe19b_ba36_a9ad_2f41, + 0x19bb_16c9_5219_dbd8, + 0x14dc_acfd_fb47_8693, + 0x115f_f58a_fff9_a8e1 + }); + + Assert.AreEqual(b, a.Invert()); + Assert.ThrowsException(() => Fp.Zero.Invert()); + } + + [TestMethod] + public void TestLexicographicLargest() + { + Assert.IsFalse(Fp.Zero.LexicographicallyLargest()); + Assert.IsFalse(Fp.One.LexicographicallyLargest()); + Assert.IsFalse(Fp.FromRawUnchecked(new ulong[] + { + 0xa1fa_ffff_fffe_5557, + 0x995b_fff9_76a3_fffe, + 0x03f4_1d24_d174_ceb4, + 0xf654_7998_c199_5dbd, + 0x778a_468f_507a_6034, + 0x0205_5993_1f7f_8103 + }).LexicographicallyLargest()); + Assert.IsTrue(Fp.FromRawUnchecked(new ulong[] + { + 0x1804_0000_0001_5554, + 0x8550_0005_3ab0_0001, + 0x633c_b57c_253c_276f, + 0x6e22_d1ec_31eb_b502, + 0xd391_6126_f2d1_4ca2, + 0x17fb_b857_1a00_6596 + }).LexicographicallyLargest()); + Assert.IsTrue(Fp.FromRawUnchecked(new ulong[] + { + 0x43f5_ffff_fffc_aaae, + 0x32b7_fff2_ed47_fffd, + 0x07e8_3a49_a2e9_9d69, + 0xeca8_f331_8332_bb7a, + 0xef14_8d1e_a0f4_c069, + 0x040a_b326_3eff_0206 + }).LexicographicallyLargest()); + } +} diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp12.cs b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp12.cs new file mode 100644 index 0000000000..238a20c6c6 --- /dev/null +++ b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp12.cs @@ -0,0 +1,347 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Fp12.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.Cryptography.BLS12_381.Tests; + +[TestClass] +public class UT_Fp12 +{ + [TestMethod] + public void TestArithmetic() + { + var a = new Fp12(new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x47f9_cb98_b1b8_2d58, + 0x5fe9_11eb_a3aa_1d9d, + 0x96bf_1b5f_4dd8_1db3, + 0x8100_d27c_c925_9f5b, + 0xafa2_0b96_7464_0eab, + 0x09bb_cea7_d8d9_497d + }), Fp.FromRawUnchecked(new ulong[] + { + 0x0303_cb98_b166_2daa, + 0xd931_10aa_0a62_1d5a, + 0xbfa9_820c_5be4_a468, + 0x0ba3_643e_cb05_a348, + 0xdc35_34bb_1f1c_25a6, + 0x06c3_05bb_19c0_e1c1 + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x46f9_cb98_b162_d858, + 0x0be9_109c_f7aa_1d57, + 0xc791_bc55_fece_41d2, + 0xf84c_5770_4e38_5ec2, + 0xcb49_c1d9_c010_e60f, + 0x0acd_b8e1_58bf_e3c8 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x8aef_cb98_b15f_8306, + 0x3ea1_108f_e4f2_1d54, + 0xcf79_f69f_a1b7_df3b, + 0xe4f5_4aa1_d16b_1a3c, + 0xba5e_4ef8_6105_a679, + 0x0ed8_6c07_97be_e5cf + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xcee5_cb98_b15c_2db4, + 0x7159_1082_d23a_1d51, + 0xd762_30e9_44a1_7ca4, + 0xd19e_3dd3_549d_d5b6, + 0xa972_dc17_01fa_66e3, + 0x12e3_1f2d_d6bd_e7d6 + }), Fp.FromRawUnchecked(new ulong[] + { + 0xad2a_cb98_b173_2d9d, + 0x2cfd_10dd_0696_1d64, + 0x0739_6b86_c6ef_24e8, + 0xbd76_e2fd_b1bf_c820, + 0x6afe_a7f6_de94_d0d5, + 0x1099_4b0c_5744_c040 + }))), new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x47f9_cb98_b1b8_2d58, + 0x5fe9_11eb_a3aa_1d9d, + 0x96bf_1b5f_4dd8_1db3, + 0x8100_d27c_c925_9f5b, + 0xafa2_0b96_7464_0eab, + 0x09bb_cea7_d8d9_497d + }), Fp.FromRawUnchecked(new ulong[] + { + 0x0303_cb98_b166_2daa, + 0xd931_10aa_0a62_1d5a, + 0xbfa9_820c_5be4_a468, + 0x0ba3_643e_cb05_a348, + 0xdc35_34bb_1f1c_25a6, + 0x06c3_05bb_19c0_e1c1 + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x46f9_cb98_b162_d858, + 0x0be9_109c_f7aa_1d57, + 0xc791_bc55_fece_41d2, + 0xf84c_5770_4e38_5ec2, + 0xcb49_c1d9_c010_e60f, + 0x0acd_b8e1_58bf_e3c8 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x8aef_cb98_b15f_8306, + 0x3ea1_108f_e4f2_1d54, + 0xcf79_f69f_a1b7_df3b, + 0xe4f5_4aa1_d16b_1a3c, + 0xba5e_4ef8_6105_a679, + 0x0ed8_6c07_97be_e5cf + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xcee5_cb98_b15c_2db4, + 0x7159_1082_d23a_1d51, + 0xd762_30e9_44a1_7ca4, + 0xd19e_3dd3_549d_d5b6, + 0xa972_dc17_01fa_66e3, + 0x12e3_1f2d_d6bd_e7d6 + }), Fp.FromRawUnchecked(new ulong[] + { + 0xad2a_cb98_b173_2d9d, + 0x2cfd_10dd_0696_1d64, + 0x0739_6b86_c6ef_24e8, + 0xbd76_e2fd_b1bf_c820, + 0x6afe_a7f6_de94_d0d5, + 0x1099_4b0c_5744_c040 + })))); + + var b = new Fp12(new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x47f9_cb98_b1b8_2d58, + 0x5fe9_11eb_a3aa_1d9d, + 0x96bf_1b5f_4dd8_1db3, + 0x8100_d272_c925_9f5b, + 0xafa2_0b96_7464_0eab, + 0x09bb_cea7_d8d9_497d + }), Fp.FromRawUnchecked(new ulong[] + { + 0x0303_cb98_b166_2daa, + 0xd931_10aa_0a62_1d5a, + 0xbfa9_820c_5be4_a468, + 0x0ba3_643e_cb05_a348, + 0xdc35_34bb_1f1c_25a6, + 0x06c3_05bb_19c0_e1c1 + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x46f9_cb98_b162_d858, + 0x0be9_109c_f7aa_1d57, + 0xc791_bc55_fece_41d2, + 0xf84c_5770_4e38_5ec2, + 0xcb49_c1d9_c010_e60f, + 0x0acd_b8e1_58bf_e348 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x8aef_cb98_b15f_8306, + 0x3ea1_108f_e4f2_1d54, + 0xcf79_f69f_a1b7_df3b, + 0xe4f5_4aa1_d16b_1a3c, + 0xba5e_4ef8_6105_a679, + 0x0ed8_6c07_97be_e5cf + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xcee5_cb98_b15c_2db4, + 0x7159_1082_d23a_1d51, + 0xd762_30e9_44a1_7ca4, + 0xd19e_3dd3_549d_d5b6, + 0xa972_dc17_01fa_66e3, + 0x12e3_1f2d_d6bd_e7d6 + }), Fp.FromRawUnchecked(new ulong[] + { + 0xad2a_cb98_b173_2d9d, + 0x2cfd_10dd_0696_1d64, + 0x0739_6b86_c6ef_24e8, + 0xbd76_e2fd_b1bf_c820, + 0x6afe_a7f6_de94_d0d5, + 0x1099_4b0c_5744_c040 + }))), new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x47f9_cb98_b1b8_2d58, + 0x5fe9_11eb_a3aa_1d9d, + 0x96bf_1b5f_4dd2_1db3, + 0x8100_d27c_c925_9f5b, + 0xafa2_0b96_7464_0eab, + 0x09bb_cea7_d8d9_497d + }), Fp.FromRawUnchecked(new ulong[] + { + 0x0303_cb98_b166_2daa, + 0xd931_10aa_0a62_1d5a, + 0xbfa9_820c_5be4_a468, + 0x0ba3_643e_cb05_a348, + 0xdc35_34bb_1f1c_25a6, + 0x06c3_05bb_19c0_e1c1 + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x46f9_cb98_b162_d858, + 0x0be9_109c_f7aa_1d57, + 0xc791_bc55_fece_41d2, + 0xf84c_5770_4e38_5ec2, + 0xcb49_c1d9_c010_e60f, + 0x0acd_b8e1_58bf_e3c8 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x8aef_cb98_b15f_8306, + 0x3ea1_108f_e4f2_1d54, + 0xcf79_f69f_a117_df3b, + 0xe4f5_4aa1_d16b_1a3c, + 0xba5e_4ef8_6105_a679, + 0x0ed8_6c07_97be_e5cf + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xcee5_cb98_b15c_2db4, + 0x7159_1082_d23a_1d51, + 0xd762_30e9_44a1_7ca4, + 0xd19e_3dd3_549d_d5b6, + 0xa972_dc17_01fa_66e3, + 0x12e3_1f2d_d6bd_e7d6 + }), Fp.FromRawUnchecked(new ulong[] + { + 0xad2a_cb98_b173_2d9d, + 0x2cfd_10dd_0696_1d64, + 0x0739_6b86_c6ef_24e8, + 0xbd76_e2fd_b1bf_c820, + 0x6afe_a7f6_de94_d0d5, + 0x1099_4b0c_5744_c040 + })))); + + var c = new Fp12(new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x47f9_cb98_71b8_2d58, + 0x5fe9_11eb_a3aa_1d9d, + 0x96bf_1b5f_4dd8_1db3, + 0x8100_d27c_c925_9f5b, + 0xafa2_0b96_7464_0eab, + 0x09bb_cea7_d8d9_497d + }), Fp.FromRawUnchecked(new ulong[] + { + 0x0303_cb98_b166_2daa, + 0xd931_10aa_0a62_1d5a, + 0xbfa9_820c_5be4_a468, + 0x0ba3_643e_cb05_a348, + 0xdc35_34bb_1f1c_25a6, + 0x06c3_05bb_19c0_e1c1 + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x46f9_cb98_b162_d858, + 0x0be9_109c_f7aa_1d57, + 0x7791_bc55_fece_41d2, + 0xf84c_5770_4e38_5ec2, + 0xcb49_c1d9_c010_e60f, + 0x0acd_b8e1_58bf_e3c8 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x8aef_cb98_b15f_8306, + 0x3ea1_108f_e4f2_1d54, + 0xcf79_f69f_a1b7_df3b, + 0xe4f5_4aa1_d16b_133c, + 0xba5e_4ef8_6105_a679, + 0x0ed8_6c07_97be_e5cf + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xcee5_cb98_b15c_2db4, + 0x7159_1082_d23a_1d51, + 0xd762_40e9_44a1_7ca4, + 0xd19e_3dd3_549d_d5b6, + 0xa972_dc17_01fa_66e3, + 0x12e3_1f2d_d6bd_e7d6 + }), Fp.FromRawUnchecked(new ulong[] + { + 0xad2a_cb98_b173_2d9d, + 0x2cfd_10dd_0696_1d64, + 0x0739_6b86_c6ef_24e8, + 0xbd76_e2fd_b1bf_c820, + 0x6afe_a7f6_de94_d0d5, + 0x1099_4b0c_1744_c040 + }))), new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x47f9_cb98_b1b8_2d58, + 0x5fe9_11eb_a3aa_1d9d, + 0x96bf_1b5f_4dd8_1db3, + 0x8100_d27c_c925_9f5b, + 0xafa2_0b96_7464_0eab, + 0x09bb_cea7_d8d9_497d + }), Fp.FromRawUnchecked(new ulong[] + { + 0x0303_cb98_b166_2daa, + 0xd931_10aa_0a62_1d5a, + 0xbfa9_820c_5be4_a468, + 0x0ba3_643e_cb05_a348, + 0xdc35_34bb_1f1c_25a6, + 0x06c3_05bb_19c0_e1c1 + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x46f9_cb98_b162_d858, + 0x0be9_109c_f7aa_1d57, + 0xc791_bc55_fece_41d2, + 0xf84c_5770_4e38_5ec2, + 0xcb49_c1d3_c010_e60f, + 0x0acd_b8e1_58bf_e3c8 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x8aef_cb98_b15f_8306, + 0x3ea1_108f_e4f2_1d54, + 0xcf79_f69f_a1b7_df3b, + 0xe4f5_4aa1_d16b_1a3c, + 0xba5e_4ef8_6105_a679, + 0x0ed8_6c07_97be_e5cf + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xcee5_cb98_b15c_2db4, + 0x7159_1082_d23a_1d51, + 0xd762_30e9_44a1_7ca4, + 0xd19e_3dd3_549d_d5b6, + 0xa972_dc17_01fa_66e3, + 0x12e3_1f2d_d6bd_e7d6 + }), Fp.FromRawUnchecked(new ulong[] + { + 0xad2a_cb98_b173_2d9d, + 0x2cfd_10dd_0696_1d64, + 0x0739_6b86_c6ef_24e8, + 0xbd76_e2fd_b1bf_c820, + 0x6afe_a7f6_de94_d0d5, + 0x1099_4b0c_5744_1040 + })))); + + // because a and b and c are similar to each other and + // I was lazy, this is just some arbitrary way to make + // them a little more different + a = a.Square().Invert().Square() + c; + b = b.Square().Invert().Square() + a; + c = c.Square().Invert().Square() + b; + + Assert.AreEqual(a * a, a.Square()); + Assert.AreEqual(b * b, b.Square()); + Assert.AreEqual(c * c, c.Square()); + + Assert.AreEqual((a + b) * c.Square(), (c * c * a) + (c * c * b)); + + Assert.AreEqual(a.Invert() * b.Invert(), (a * b).Invert()); + Assert.AreEqual(Fp12.One, a.Invert() * a); + + Assert.AreNotEqual(a, a.FrobeniusMap()); + Assert.AreEqual( + a, + a.FrobeniusMap() + .FrobeniusMap() + .FrobeniusMap() + .FrobeniusMap() + .FrobeniusMap() + .FrobeniusMap() + .FrobeniusMap() + .FrobeniusMap() + .FrobeniusMap() + .FrobeniusMap() + .FrobeniusMap() + .FrobeniusMap() + ); + } +} diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp2.cs b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp2.cs new file mode 100644 index 0000000000..8e8781b834 --- /dev/null +++ b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp2.cs @@ -0,0 +1,493 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Fp2.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Collections; +using static Neo.Cryptography.BLS12_381.ConstantTimeUtility; + +namespace Neo.Cryptography.BLS12_381.Tests; + +[TestClass] +public class UT_Fp2 +{ + [TestMethod] + public void TestConditionalSelection() + { + var a = new Fp2( + Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }), + Fp.FromRawUnchecked(new ulong[] { 7, 8, 9, 10, 11, 12 }) + ); + var b = new Fp2( + Fp.FromRawUnchecked(new ulong[] { 13, 14, 15, 16, 17, 18 }), + Fp.FromRawUnchecked(new ulong[] { 19, 20, 21, 22, 23, 24 }) + ); + + Assert.AreEqual(a, ConditionalSelect(in a, in b, false)); + Assert.AreEqual(b, ConditionalSelect(in a, in b, true)); + } + + [TestMethod] + public void TestEquality() + { + static bool IsEqual(in Fp2 a, in Fp2 b) + { + var eq = StructuralComparisons.StructuralEqualityComparer.Equals(a, b); + var ct_eq = a == b; + Assert.AreEqual(eq, ct_eq); + return eq; + } + + Assert.IsTrue(IsEqual( + new Fp2(Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 7, 8, 9, 10, 11, 12 })), + new Fp2(Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 7, 8, 9, 10, 11, 12 })) + )); + + Assert.IsFalse(IsEqual( + new Fp2(Fp.FromRawUnchecked(new ulong[] { 2, 2, 3, 4, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 7, 8, 9, 10, 11, 12 })), + new Fp2(Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 7, 8, 9, 10, 11, 12 })) + )); + + Assert.IsFalse(IsEqual( + new Fp2(Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 2, 8, 9, 10, 11, 12 })), + new Fp2(Fp.FromRawUnchecked(new ulong[] { 1, 2, 3, 4, 5, 6 }), Fp.FromRawUnchecked(new ulong[] { 7, 8, 9, 10, 11, 12 })) + )); + } + + [TestMethod] + public void TestSquaring() + { + var a = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xc9a2_1831_63ee_70d4, + 0xbc37_70a7_196b_5c91, + 0xa247_f8c1_304c_5f44, + 0xb01f_c2a3_726c_80b5, + 0xe1d2_93e5_bbd9_19c9, + 0x04b7_8e80_020e_f2ca, + }), Fp.FromRawUnchecked(new ulong[] + { + 0x952e_a446_0462_618f, + 0x238d_5edd_f025_c62f, + 0xf6c9_4b01_2ea9_2e72, + 0x03ce_24ea_c1c9_3808, + 0x0559_50f9_45da_483c, + 0x010a_768d_0df4_eabc, + })); + var b = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xa1e0_9175_a4d2_c1fe, + 0x8b33_acfc_204e_ff12, + 0xe244_15a1_1b45_6e42, + 0x61d9_96b1_b6ee_1936, + 0x1164_dbe8_667c_853c, + 0x0788_557a_cc7d_9c79, + }), Fp.FromRawUnchecked(new ulong[] + { + 0xda6a_87cc_6f48_fa36, + 0x0fc7_b488_277c_1903, + 0x9445_ac4a_dc44_8187, + 0x0261_6d5b_c909_9209, + 0xdbed_4677_2db5_8d48, + 0x11b9_4d50_76c7_b7b1, + })); + + Assert.AreEqual(a.Square(), b); + } + + [TestMethod] + public void TestMultiplication() + { + var a = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xc9a2_1831_63ee_70d4, + 0xbc37_70a7_196b_5c91, + 0xa247_f8c1_304c_5f44, + 0xb01f_c2a3_726c_80b5, + 0xe1d2_93e5_bbd9_19c9, + 0x04b7_8e80_020e_f2ca, + }), Fp.FromRawUnchecked(new ulong[] + { + 0x952e_a446_0462_618f, + 0x238d_5edd_f025_c62f, + 0xf6c9_4b01_2ea9_2e72, + 0x03ce_24ea_c1c9_3808, + 0x0559_50f9_45da_483c, + 0x010a_768d_0df4_eabc, + })); + var b = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xa1e0_9175_a4d2_c1fe, + 0x8b33_acfc_204e_ff12, + 0xe244_15a1_1b45_6e42, + 0x61d9_96b1_b6ee_1936, + 0x1164_dbe8_667c_853c, + 0x0788_557a_cc7d_9c79, + }), Fp.FromRawUnchecked(new ulong[] + { + 0xda6a_87cc_6f48_fa36, + 0x0fc7_b488_277c_1903, + 0x9445_ac4a_dc44_8187, + 0x0261_6d5b_c909_9209, + 0xdbed_4677_2db5_8d48, + 0x11b9_4d50_76c7_b7b1, + })); + var c = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xf597_483e_27b4_e0f7, + 0x610f_badf_811d_ae5f, + 0x8432_af91_7714_327a, + 0x6a9a_9603_cf88_f09e, + 0xf05a_7bf8_bad0_eb01, + 0x0954_9131_c003_ffae, + }), Fp.FromRawUnchecked(new ulong[] + { + 0x963b_02d0_f93d_37cd, + 0xc95c_e1cd_b30a_73d4, + 0x3087_25fa_3126_f9b8, + 0x56da_3c16_7fab_0d50, + 0x6b50_86b5_f4b6_d6af, + 0x09c3_9f06_2f18_e9f2, + })); + + Assert.AreEqual(c, a * b); + } + + [TestMethod] + public void TestAddition() + { + var a = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xc9a2_1831_63ee_70d4, + 0xbc37_70a7_196b_5c91, + 0xa247_f8c1_304c_5f44, + 0xb01f_c2a3_726c_80b5, + 0xe1d2_93e5_bbd9_19c9, + 0x04b7_8e80_020e_f2ca, + }), Fp.FromRawUnchecked(new ulong[] + { + 0x952e_a446_0462_618f, + 0x238d_5edd_f025_c62f, + 0xf6c9_4b01_2ea9_2e72, + 0x03ce_24ea_c1c9_3808, + 0x0559_50f9_45da_483c, + 0x010a_768d_0df4_eabc, + })); + var b = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xa1e0_9175_a4d2_c1fe, + 0x8b33_acfc_204e_ff12, + 0xe244_15a1_1b45_6e42, + 0x61d9_96b1_b6ee_1936, + 0x1164_dbe8_667c_853c, + 0x0788_557a_cc7d_9c79, + }), Fp.FromRawUnchecked(new ulong[] + { + 0xda6a_87cc_6f48_fa36, + 0x0fc7_b488_277c_1903, + 0x9445_ac4a_dc44_8187, + 0x0261_6d5b_c909_9209, + 0xdbed_4677_2db5_8d48, + 0x11b9_4d50_76c7_b7b1, + })); + var c = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x6b82_a9a7_08c1_32d2, + 0x476b_1da3_39ba_5ba4, + 0x848c_0e62_4b91_cd87, + 0x11f9_5955_295a_99ec, + 0xf337_6fce_2255_9f06, + 0x0c3f_e3fa_ce8c_8f43, + }), Fp.FromRawUnchecked(new ulong[] + { + 0x6f99_2c12_73ab_5bc5, + 0x3355_1366_17a1_df33, + 0x8b0e_f74c_0aed_aff9, + 0x062f_9246_8ad2_ca12, + 0xe146_9770_738f_d584, + 0x12c3_c3dd_84bc_a26d, + })); + + Assert.AreEqual(a + b, c); + } + + [TestMethod] + public void TestSubtraction() + { + var a = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xc9a2_1831_63ee_70d4, + 0xbc37_70a7_196b_5c91, + 0xa247_f8c1_304c_5f44, + 0xb01f_c2a3_726c_80b5, + 0xe1d2_93e5_bbd9_19c9, + 0x04b7_8e80_020e_f2ca, + }), Fp.FromRawUnchecked(new ulong[] + { + 0x952e_a446_0462_618f, + 0x238d_5edd_f025_c62f, + 0xf6c9_4b01_2ea9_2e72, + 0x03ce_24ea_c1c9_3808, + 0x0559_50f9_45da_483c, + 0x010a_768d_0df4_eabc, + })); + var b = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xa1e0_9175_a4d2_c1fe, + 0x8b33_acfc_204e_ff12, + 0xe244_15a1_1b45_6e42, + 0x61d9_96b1_b6ee_1936, + 0x1164_dbe8_667c_853c, + 0x0788_557a_cc7d_9c79, + }), Fp.FromRawUnchecked(new ulong[] + { + 0xda6a_87cc_6f48_fa36, + 0x0fc7_b488_277c_1903, + 0x9445_ac4a_dc44_8187, + 0x0261_6d5b_c909_9209, + 0xdbed_4677_2db5_8d48, + 0x11b9_4d50_76c7_b7b1, + })); + var c = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xe1c0_86bb_bf1b_5981, + 0x4faf_c3a9_aa70_5d7e, + 0x2734_b5c1_0bb7_e726, + 0xb2bd_7776_af03_7a3e, + 0x1b89_5fb3_98a8_4164, + 0x1730_4aef_6f11_3cec, + }), Fp.FromRawUnchecked(new ulong[] + { + 0x74c3_1c79_9519_1204, + 0x3271_aa54_79fd_ad2b, + 0xc9b4_7157_4915_a30f, + 0x65e4_0313_ec44_b8be, + 0x7487_b238_5b70_67cb, + 0x0952_3b26_d0ad_19a4, + })); + + Assert.AreEqual(c, a - b); + } + + [TestMethod] + public void TestNegation() + { + var a = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xc9a2_1831_63ee_70d4, + 0xbc37_70a7_196b_5c91, + 0xa247_f8c1_304c_5f44, + 0xb01f_c2a3_726c_80b5, + 0xe1d2_93e5_bbd9_19c9, + 0x04b7_8e80_020e_f2ca, + }), Fp.FromRawUnchecked(new ulong[] + { + 0x952e_a446_0462_618f, + 0x238d_5edd_f025_c62f, + 0xf6c9_4b01_2ea9_2e72, + 0x03ce_24ea_c1c9_3808, + 0x0559_50f9_45da_483c, + 0x010a_768d_0df4_eabc, + })); + var b = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xf05c_e7ce_9c11_39d7, + 0x6274_8f57_97e8_a36d, + 0xc4e8_d9df_c664_96df, + 0xb457_88e1_8118_9209, + 0x6949_13d0_8772_930d, + 0x1549_836a_3770_f3cf, + }), Fp.FromRawUnchecked(new ulong[] + { + 0x24d0_5bb9_fb9d_491c, + 0xfb1e_a120_c12e_39d0, + 0x7067_879f_c807_c7b1, + 0x60a9_269a_31bb_dab6, + 0x45c2_56bc_fd71_649b, + 0x18f6_9b5d_2b8a_fbde, + })); + + Assert.AreEqual(b, -a); + } + + [TestMethod] + public void TestSqrt() + { + // a = 1488924004771393321054797166853618474668089414631333405711627789629391903630694737978065425271543178763948256226639*u + 784063022264861764559335808165825052288770346101304131934508881646553551234697082295473567906267937225174620141295 + var a = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x2bee_d146_27d7_f9e9, + 0xb661_4e06_660e_5dce, + 0x06c4_cc7c_2f91_d42c, + 0x996d_7847_4b7a_63cc, + 0xebae_bc4c_820d_574e, + 0x1886_5e12_d93f_d845, + }), Fp.FromRawUnchecked(new ulong[] + { + 0x7d82_8664_baf4_f566, + 0xd17e_6639_96ec_7339, + 0x679e_ad55_cb40_78d0, + 0xfe3b_2260_e001_ec28, + 0x3059_93d0_43d9_1b68, + 0x0626_f03c_0489_b72d, + })); + + Assert.AreEqual(a, a.Sqrt().Square()); + + // b = 5, which is a generator of the p - 1 order + // multiplicative subgroup + var b = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x6631_0000_0010_5545, + 0x2114_0040_0eec_000d, + 0x3fa7_af30_c820_e316, + 0xc52a_8b8d_6387_695d, + 0x9fb4_e61d_1e83_eac5, + 0x005c_b922_afe8_4dc7, + }), Fp.Zero); + + Assert.AreEqual(b, b.Sqrt().Square()); + + // c = 25, which is a generator of the (p - 1) / 2 order + // multiplicative subgroup + var c = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x44f6_0000_0051_ffae, + 0x86b8_0141_9948_0043, + 0xd715_9952_f1f3_794a, + 0x755d_6e3d_fe1f_fc12, + 0xd36c_d6db_5547_e905, + 0x02f8_c8ec_bf18_67bb, + }), Fp.Zero); + + Assert.AreEqual(c, c.Sqrt().Square()); + + // 2155129644831861015726826462986972654175647013268275306775721078997042729172900466542651176384766902407257452753362*u + 2796889544896299244102912275102369318775038861758288697415827248356648685135290329705805931514906495247464901062529 + // is nonsquare. + Assert.ThrowsException(() => + new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xc5fa_1bc8_fd00_d7f6, + 0x3830_ca45_4606_003b, + 0x2b28_7f11_04b1_02da, + 0xa7fb_30f2_8230_f23e, + 0x339c_db9e_e953_dbf0, + 0x0d78_ec51_d989_fc57, + }), Fp.FromRawUnchecked(new ulong[]{ + 0x27ec_4898_cf87_f613, + 0x9de1_394e_1abb_05a5, + 0x0947_f85d_c170_fc14, + 0x586f_bc69_6b61_14b7, + 0x2b34_75a4_077d_7169, + 0x13e1_c895_cc4b_6c22, + })).Sqrt()); + } + + [TestMethod] + public void TestInversion() + { + var a = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x1128_ecad_6754_9455, + 0x9e7a_1cff_3a4e_a1a8, + 0xeb20_8d51_e08b_cf27, + 0xe98a_d408_11f5_fc2b, + 0x736c_3a59_232d_511d, + 0x10ac_d42d_29cf_cbb6, + }), Fp.FromRawUnchecked(new ulong[] + { + 0xd328_e37c_c2f5_8d41, + 0x948d_f085_8a60_5869, + 0x6032_f9d5_6f93_a573, + 0x2be4_83ef_3fff_dc87, + 0x30ef_61f8_8f48_3c2a, + 0x1333_f55a_3572_5be0, + })); + + var b = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x0581_a133_3d4f_48a6, + 0x5824_2f6e_f074_8500, + 0x0292_c955_349e_6da5, + 0xba37_721d_dd95_fcd0, + 0x70d1_6790_3aa5_dfc5, + 0x1189_5e11_8b58_a9d5, + }), Fp.FromRawUnchecked(new ulong[] + { + 0x0eda_09d2_d7a8_5d17, + 0x8808_e137_a7d1_a2cf, + 0x43ae_2625_c1ff_21db, + 0xf85a_c9fd_f7a7_4c64, + 0x8fcc_dda5_b8da_9738, + 0x08e8_4f0c_b32c_d17d, + })); + Assert.AreEqual(b, a.Invert()); + + Assert.ThrowsException(() => Fp2.Zero.Invert()); + } + + [TestMethod] + public void TestLexicographicLargest() + { + Assert.IsFalse(Fp2.Zero.LexicographicallyLargest()); + Assert.IsFalse(Fp2.One.LexicographicallyLargest()); + Assert.IsTrue(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x1128_ecad_6754_9455, + 0x9e7a_1cff_3a4e_a1a8, + 0xeb20_8d51_e08b_cf27, + 0xe98a_d408_11f5_fc2b, + 0x736c_3a59_232d_511d, + 0x10ac_d42d_29cf_cbb6, + }), Fp.FromRawUnchecked(new ulong[] + { + 0xd328_e37c_c2f5_8d41, + 0x948d_f085_8a60_5869, + 0x6032_f9d5_6f93_a573, + 0x2be4_83ef_3fff_dc87, + 0x30ef_61f8_8f48_3c2a, + 0x1333_f55a_3572_5be0, + })).LexicographicallyLargest()); + Assert.IsFalse(new Fp2(-Fp.FromRawUnchecked(new ulong[] + { + 0x1128_ecad_6754_9455, + 0x9e7a_1cff_3a4e_a1a8, + 0xeb20_8d51_e08b_cf27, + 0xe98a_d408_11f5_fc2b, + 0x736c_3a59_232d_511d, + 0x10ac_d42d_29cf_cbb6, + }), -Fp.FromRawUnchecked(new ulong[] + { + 0xd328_e37c_c2f5_8d41, + 0x948d_f085_8a60_5869, + 0x6032_f9d5_6f93_a573, + 0x2be4_83ef_3fff_dc87, + 0x30ef_61f8_8f48_3c2a, + 0x1333_f55a_3572_5be0, + })).LexicographicallyLargest()); + Assert.IsFalse(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x1128_ecad_6754_9455, + 0x9e7a_1cff_3a4e_a1a8, + 0xeb20_8d51_e08b_cf27, + 0xe98a_d408_11f5_fc2b, + 0x736c_3a59_232d_511d, + 0x10ac_d42d_29cf_cbb6, + }), Fp.Zero).LexicographicallyLargest()); + Assert.IsTrue(new Fp2(-Fp.FromRawUnchecked(new ulong[] + { + 0x1128_ecad_6754_9455, + 0x9e7a_1cff_3a4e_a1a8, + 0xeb20_8d51_e08b_cf27, + 0xe98a_d408_11f5_fc2b, + 0x736c_3a59_232d_511d, + 0x10ac_d42d_29cf_cbb6, + }), Fp.Zero).LexicographicallyLargest()); + } +} diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp6.cs b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp6.cs new file mode 100644 index 0000000000..6fcc63fc97 --- /dev/null +++ b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Fp6.cs @@ -0,0 +1,179 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Fp6.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.Cryptography.BLS12_381.Tests; + +[TestClass] +public class UT_Fp6 +{ + [TestMethod] + public void TestArithmetic() + { + var a = new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x47f9_cb98_b1b8_2d58, + 0x5fe9_11eb_a3aa_1d9d, + 0x96bf_1b5f_4dd8_1db3, + 0x8100_d27c_c925_9f5b, + 0xafa2_0b96_7464_0eab, + 0x09bb_cea7_d8d9_497d + }), c1: Fp.FromRawUnchecked(new ulong[] + { + 0x0303_cb98_b166_2daa, + 0xd931_10aa_0a62_1d5a, + 0xbfa9_820c_5be4_a468, + 0x0ba3_643e_cb05_a348, + 0xdc35_34bb_1f1c_25a6, + 0x06c3_05bb_19c0_e1c1 + })), c1: new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x46f9_cb98_b162_d858, + 0x0be9_109c_f7aa_1d57, + 0xc791_bc55_fece_41d2, + 0xf84c_5770_4e38_5ec2, + 0xcb49_c1d9_c010_e60f, + 0x0acd_b8e1_58bf_e3c8 + }), c1: Fp.FromRawUnchecked(new ulong[] + { + 0x8aef_cb98_b15f_8306, + 0x3ea1_108f_e4f2_1d54, + 0xcf79_f69f_a1b7_df3b, + 0xe4f5_4aa1_d16b_1a3c, + 0xba5e_4ef8_6105_a679, + 0x0ed8_6c07_97be_e5cf + })), c2: new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xcee5_cb98_b15c_2db4, + 0x7159_1082_d23a_1d51, + 0xd762_30e9_44a1_7ca4, + 0xd19e_3dd3_549d_d5b6, + 0xa972_dc17_01fa_66e3, + 0x12e3_1f2d_d6bd_e7d6 + }), c1: Fp.FromRawUnchecked(new ulong[] + { + 0xad2a_cb98_b173_2d9d, + 0x2cfd_10dd_0696_1d64, + 0x0739_6b86_c6ef_24e8, + 0xbd76_e2fd_b1bf_c820, + 0x6afe_a7f6_de94_d0d5, + 0x1099_4b0c_5744_c040 + }))); + + var b = new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xf120_cb98_b16f_d84b, + 0x5fb5_10cf_f3de_1d61, + 0x0f21_a5d0_69d8_c251, + 0xaa1f_d62f_34f2_839a, + 0x5a13_3515_7f89_913f, + 0x14a3_fe32_9643_c247 + }), c1: Fp.FromRawUnchecked(new ulong[] + { + 0x3516_cb98_b16c_82f9, + 0x926d_10c2_e126_1d5f, + 0x1709_e01a_0cc2_5fba, + 0x96c8_c960_b825_3f14, + 0x4927_c234_207e_51a9, + 0x18ae_b158_d542_c44e + })), c1: new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xbf0d_cb98_b169_82fc, + 0xa679_10b7_1d1a_1d5c, + 0xb7c1_47c2_b8fb_06ff, + 0x1efa_710d_47d2_e7ce, + 0xed20_a79c_7e27_653c, + 0x02b8_5294_dac1_dfba + }), c1: Fp.FromRawUnchecked(new ulong[] + { + 0x9d52_cb98_b180_82e5, + 0x621d_1111_5176_1d6f, + 0xe798_8260_3b48_af43, + 0x0ad3_1637_a4f4_da37, + 0xaeac_737c_5ac1_cf2e, + 0x006e_7e73_5b48_b824 + })), c2: new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xe148_cb98_b17d_2d93, + 0x94d5_1104_3ebe_1d6c, + 0xef80_bca9_de32_4cac, + 0xf77c_0969_2827_95b1, + 0x9dc1_009a_fbb6_8f97, + 0x0479_3199_9a47_ba2b + }), c1: Fp.FromRawUnchecked(new ulong[] + { + 0x253e_cb98_b179_d841, + 0xc78d_10f7_2c06_1d6a, + 0xf768_f6f3_811b_ea15, + 0xe424_fc9a_ab5a_512b, + 0x8cd5_8db9_9cab_5001, + 0x0883_e4bf_d946_bc32 + }))); + + var c = new Fp6(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x6934_cb98_b176_82ef, + 0xfa45_10ea_194e_1d67, + 0xff51_313d_2405_877e, + 0xd0cd_efcc_2e8d_0ca5, + 0x7bea_1ad8_3da0_106b, + 0x0c8e_97e6_1845_be39 + }), c1: Fp.FromRawUnchecked(new ulong[] + { + 0x4779_cb98_b18d_82d8, + 0xb5e9_1144_4daa_1d7a, + 0x2f28_6bda_a653_2fc2, + 0xbca6_94f6_8bae_ff0f, + 0x3d75_e6b8_1a3a_7a5d, + 0x0a44_c3c4_98cc_96a3 + })), c1: new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x8b6f_cb98_b18a_2d86, + 0xe8a1_1137_3af2_1d77, + 0x3710_a624_493c_cd2b, + 0xa94f_8828_0ee1_ba89, + 0x2c8a_73d6_bb2f_3ac7, + 0x0e4f_76ea_d7cb_98aa + }), c1: Fp.FromRawUnchecked(new ulong[] + { + 0xcf65_cb98_b186_d834, + 0x1b59_112a_283a_1d74, + 0x3ef8_e06d_ec26_6a95, + 0x95f8_7b59_9214_7603, + 0x1b9f_00f5_5c23_fb31, + 0x125a_2a11_16ca_9ab1 + })), c2: new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x135b_cb98_b183_82e2, + 0x4e11_111d_1582_1d72, + 0x46e1_1ab7_8f10_07fe, + 0x82a1_6e8b_1547_317d, + 0x0ab3_8e13_fd18_bb9b, + 0x1664_dd37_55c9_9cb8 + }), c1: Fp.FromRawUnchecked(new ulong[] + { + 0xce65_cb98_b131_8334, + 0xc759_0fdb_7c3a_1d2e, + 0x6fcb_8164_9d1c_8eb3, + 0x0d44_004d_1727_356a, + 0x3746_b738_a7d0_d296, + 0x136c_144a_96b1_34fc + }))); + + Assert.AreEqual(a * a, a.Square()); + Assert.AreEqual(b * b, b.Square()); + Assert.AreEqual(c * c, c.Square()); + + Assert.AreEqual((a + b) * c.Square(), (c * c * a) + (c * c * b)); + + Assert.AreEqual(a.Invert() * b.Invert(), (a * b).Invert()); + Assert.AreEqual(Fp6.One, a.Invert() * a); + } +} diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/UT_G1.cs b/tests/Neo.Cryptography.BLS12_381.Tests/UT_G1.cs new file mode 100644 index 0000000000..ab1a598b3c --- /dev/null +++ b/tests/Neo.Cryptography.BLS12_381.Tests/UT_G1.cs @@ -0,0 +1,603 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_G1.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using static Neo.Cryptography.BLS12_381.Constants; +using static Neo.Cryptography.BLS12_381.ConstantTimeUtility; +using static Neo.Cryptography.BLS12_381.G1Constants; + +namespace Neo.Cryptography.BLS12_381.Tests; + +[TestClass] +public class UT_G1 +{ + [TestMethod] + public void TestBeta() + { + Assert.AreEqual(Fp.FromBytes(new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x19, 0x67, 0x2f, 0xdf, 0x76, + 0xce, 0x51, 0xba, 0x69, 0xc6, 0x07, 0x6a, 0x0f, 0x77, 0xea, 0xdd, 0xb3, 0xa9, 0x3b, + 0xe6, 0xf8, 0x96, 0x88, 0xde, 0x17, 0xd8, 0x13, 0x62, 0x0a, 0x00, 0x02, 0x2e, 0x01, + 0xff, 0xff, 0xff, 0xfe, 0xff, 0xfe + }), BETA); + Assert.AreNotEqual(Fp.One, BETA); + Assert.AreNotEqual(Fp.One, BETA * BETA); + Assert.AreEqual(Fp.One, BETA * BETA * BETA); + } + + [TestMethod] + public void TestIsOnCurve() + { + Assert.IsTrue(G1Affine.Identity.IsOnCurve); + Assert.IsTrue(G1Affine.Generator.IsOnCurve); + Assert.IsTrue(G1Projective.Identity.IsOnCurve); + Assert.IsTrue(G1Projective.Generator.IsOnCurve); + + Fp z = Fp.FromRawUnchecked(new ulong[] + { + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e + }); + + var gen = G1Affine.Generator; + G1Projective test = new(gen.X * z, gen.Y * z, in z); + + Assert.IsTrue(test.IsOnCurve); + + test = new(in z, in test.Y, in test.Z); + Assert.IsFalse(test.IsOnCurve); + } + + [TestMethod] + public void TestAffinePointEquality() + { + var a = G1Affine.Generator; + var b = G1Affine.Identity; + + Assert.AreEqual(a, a); + Assert.AreEqual(b, b); + Assert.AreNotEqual(a, b); + } + + [TestMethod] + public void TestProjectivePointEquality() + { + var a = G1Projective.Generator; + var b = G1Projective.Identity; + + Assert.AreEqual(a, a); + Assert.AreEqual(b, b); + Assert.AreNotEqual(a, b); + + Fp z = Fp.FromRawUnchecked(new ulong[] + { + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e + }); + + G1Projective c = new(a.X * z, a.Y * z, in z); + Assert.IsTrue(c.IsOnCurve); + + Assert.AreEqual(a, c); + Assert.AreNotEqual(b, c); + + c = new(in c.X, -c.Y, in c.Z); + Assert.IsTrue(c.IsOnCurve); + + Assert.AreNotEqual(a, c); + Assert.AreNotEqual(b, c); + + c = new(in z, -c.Y, in c.Z); + Assert.IsFalse(c.IsOnCurve); + Assert.AreNotEqual(a, b); + Assert.AreNotEqual(a, c); + Assert.AreNotEqual(b, c); + } + + [TestMethod] + public void TestConditionallySelectAffine() + { + var a = G1Affine.Generator; + var b = G1Affine.Identity; + + Assert.AreEqual(a, ConditionalSelect(in a, in b, false)); + Assert.AreEqual(b, ConditionalSelect(in a, in b, true)); + } + + [TestMethod] + public void TestConditionallySelectProjective() + { + var a = G1Projective.Generator; + var b = G1Projective.Identity; + + Assert.AreEqual(a, ConditionalSelect(in a, in b, false)); + Assert.AreEqual(b, ConditionalSelect(in a, in b, true)); + } + + [TestMethod] + public void TestProjectiveToAffine() + { + var a = G1Projective.Generator; + var b = G1Projective.Identity; + + Assert.IsTrue(new G1Affine(a).IsOnCurve); + Assert.IsFalse(new G1Affine(a).IsIdentity); + Assert.IsTrue(new G1Affine(b).IsOnCurve); + Assert.IsTrue(new G1Affine(b).IsIdentity); + + Fp z = Fp.FromRawUnchecked(new ulong[] + { + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e + }); + + G1Projective c = new(a.X * z, a.Y * z, in z); + + Assert.AreEqual(G1Affine.Generator, new G1Affine(c)); + } + + [TestMethod] + public void TestAffineToProjective() + { + var a = G1Affine.Generator; + var b = G1Affine.Identity; + + Assert.IsTrue(new G1Projective(a).IsOnCurve); + Assert.IsFalse(new G1Projective(a).IsIdentity); + Assert.IsTrue(new G1Projective(b).IsOnCurve); + Assert.IsTrue(new G1Projective(b).IsIdentity); + } + + [TestMethod] + public void TestDoubling() + { + { + var tmp = G1Projective.Identity.Double(); + Assert.IsTrue(tmp.IsIdentity); + Assert.IsTrue(tmp.IsOnCurve); + } + { + var tmp = G1Projective.Generator.Double(); + Assert.IsFalse(tmp.IsIdentity); + Assert.IsTrue(tmp.IsOnCurve); + + Assert.AreEqual(new G1Affine(Fp.FromRawUnchecked(new ulong[] + { + 0x53e9_78ce_58a9_ba3c, + 0x3ea0_583c_4f3d_65f9, + 0x4d20_bb47_f001_2960, + 0xa54c_664a_e5b2_b5d9, + 0x26b5_52a3_9d7e_b21f, + 0x0008_895d_26e6_8785 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x7011_0b32_9829_3940, + 0xda33_c539_3f1f_6afc, + 0xb86e_dfd1_6a5a_a785, + 0xaec6_d1c9_e7b1_c895, + 0x25cf_c2b5_22d1_1720, + 0x0636_1c83_f8d0_9b15 + })), new G1Affine(tmp)); + } + } + + [TestMethod] + public void TestProjectiveAddition() + { + { + var a = G1Projective.Identity; + var b = G1Projective.Identity; + var c = a + b; + Assert.IsTrue(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + } + { + var a = G1Projective.Identity; + var b = G1Projective.Generator; + + Fp z = Fp.FromRawUnchecked(new ulong[] + { + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e + }); + + b = new(b.X * z, b.Y * z, in z); + var c = a + b; + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + Assert.AreEqual(G1Projective.Generator, c); + } + { + var a = G1Projective.Identity; + var b = G1Projective.Generator; + + Fp z = Fp.FromRawUnchecked(new ulong[] + { + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e + }); + + b = new(b.X * z, b.Y * z, in z); + var c = b + a; + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + Assert.AreEqual(G1Projective.Generator, c); + } + { + var a = G1Projective.Generator.Double().Double(); // 4P + var b = G1Projective.Generator.Double(); // 2P + var c = a + b; + + var d = G1Projective.Generator; + for (int i = 0; i < 5; i++) + { + d += G1Projective.Generator; + } + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + Assert.IsFalse(d.IsIdentity); + Assert.IsTrue(d.IsOnCurve); + Assert.AreEqual(c, d); + } + { + Fp beta = Fp.FromRawUnchecked(new ulong[] + { + 0xcd03_c9e4_8671_f071, + 0x5dab_2246_1fcd_a5d2, + 0x5870_42af_d385_1b95, + 0x8eb6_0ebe_01ba_cb9e, + 0x03f9_7d6e_83d0_50d2, + 0x18f0_2065_5463_8741 + }); + beta = beta.Square(); + var a = G1Projective.Generator.Double().Double(); + var b = new G1Projective(a.X * beta, -a.Y, in a.Z); + Assert.IsTrue(a.IsOnCurve); + Assert.IsTrue(b.IsOnCurve); + + var c = a + b; + Assert.AreEqual(new G1Affine(new G1Projective(Fp.FromRawUnchecked(new ulong[] + { + 0x29e1_e987_ef68_f2d0, + 0xc5f3_ec53_1db0_3233, + 0xacd6_c4b6_ca19_730f, + 0x18ad_9e82_7bc2_bab7, + 0x46e3_b2c5_785c_c7a9, + 0x07e5_71d4_2d22_ddd6 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x94d1_17a7_e5a5_39e7, + 0x8e17_ef67_3d4b_5d22, + 0x9d74_6aaf_508a_33ea, + 0x8c6d_883d_2516_c9a2, + 0x0bc3_b8d5_fb04_47f7, + 0x07bf_a4c7_210f_4f44, + }), in Fp.One)), new G1Affine(c)); + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + } + } + + [TestMethod] + public void TestMixedAddition() + { + { + var a = G1Affine.Identity; + var b = G1Projective.Identity; + var c = a + b; + Assert.IsTrue(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + } + { + var a = G1Affine.Identity; + var b = G1Projective.Generator; + + Fp z = Fp.FromRawUnchecked(new ulong[] + { + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e + }); + + b = new(b.X * z, b.Y * z, in z); + var c = a + b; + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + Assert.AreEqual(G1Projective.Generator, c); + } + { + var a = G1Affine.Identity; + var b = G1Projective.Generator; + + Fp z = Fp.FromRawUnchecked(new ulong[] + { + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e + }); + + b = new(b.X * z, b.Y * z, in z); + var c = b + a; + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + Assert.AreEqual(G1Projective.Generator, c); + } + { + var a = G1Projective.Generator.Double().Double(); // 4P + var b = G1Projective.Generator.Double(); // 2P + var c = a + b; + + var d = G1Projective.Generator; + for (int i = 0; i < 5; i++) + { + d += G1Affine.Generator; + } + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + Assert.IsFalse(d.IsIdentity); + Assert.IsTrue(d.IsOnCurve); + Assert.AreEqual(c, d); + } + { + Fp beta = Fp.FromRawUnchecked(new ulong[] + { + 0xcd03_c9e4_8671_f071, + 0x5dab_2246_1fcd_a5d2, + 0x5870_42af_d385_1b95, + 0x8eb6_0ebe_01ba_cb9e, + 0x03f9_7d6e_83d0_50d2, + 0x18f0_2065_5463_8741 + }); + beta = beta.Square(); + var a = G1Projective.Generator.Double().Double(); + var b = new G1Projective(a.X * beta, -a.Y, in a.Z); + var a2 = new G1Affine(a); + Assert.IsTrue(a2.IsOnCurve); + Assert.IsTrue(b.IsOnCurve); + + var c = a2 + b; + Assert.AreEqual(new G1Affine(new G1Projective(Fp.FromRawUnchecked(new ulong[] + { + 0x29e1_e987_ef68_f2d0, + 0xc5f3_ec53_1db0_3233, + 0xacd6_c4b6_ca19_730f, + 0x18ad_9e82_7bc2_bab7, + 0x46e3_b2c5_785c_c7a9, + 0x07e5_71d4_2d22_ddd6 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x94d1_17a7_e5a5_39e7, + 0x8e17_ef67_3d4b_5d22, + 0x9d74_6aaf_508a_33ea, + 0x8c6d_883d_2516_c9a2, + 0x0bc3_b8d5_fb04_47f7, + 0x07bf_a4c7_210f_4f44 + }), Fp.One)), new G1Affine(c)); + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + } + } + + [TestMethod] + public void TestProjectiveNegationAndSubtraction() + { + var a = G1Projective.Generator.Double(); + Assert.AreEqual(a + (-a), G1Projective.Identity); + Assert.AreEqual(a + (-a), a - a); + } + + [TestMethod] + public void TestAffineNegationAndSubtraction() + { + var a = G1Affine.Generator; + Assert.AreEqual(G1Projective.Identity, new G1Projective(a) + (-a)); + Assert.AreEqual(new G1Projective(a) + (-a), new G1Projective(a) - a); + } + + [TestMethod] + public void TestProjectiveScalarMultiplication() + { + var g = G1Projective.Generator; + var a = Scalar.FromRaw(new ulong[] + { + 0x2b56_8297_a56d_a71c, + 0xd8c3_9ecb_0ef3_75d1, + 0x435c_38da_67bf_bf96, + 0x8088_a050_26b6_59b2 + }); + var b = Scalar.FromRaw(new ulong[] + { + 0x785f_dd9b_26ef_8b85, + 0xc997_f258_3769_5c18, + 0x4c8d_bc39_e7b7_56c1, + 0x70d9_b6cc_6d87_df20 + }); + var c = a * b; + + Assert.AreEqual(g * a * b, g * c); + } + + [TestMethod] + public void TestAffineScalarMultiplication() + { + var g = G1Affine.Generator; + var a = Scalar.FromRaw(new ulong[] + { + 0x2b56_8297_a56d_a71c, + 0xd8c3_9ecb_0ef3_75d1, + 0x435c_38da_67bf_bf96, + 0x8088_a050_26b6_59b2 + }); + var b = Scalar.FromRaw(new ulong[] + { + 0x785f_dd9b_26ef_8b85, + 0xc997_f258_3769_5c18, + 0x4c8d_bc39_e7b7_56c1, + 0x70d9_b6cc_6d87_df20 + }); + var c = a * b; + + Assert.AreEqual(new G1Affine(g * a) * b, g * c); + } + + [TestMethod] + public void TestIsTorsionFree() + { + var a = new G1Affine(Fp.FromRawUnchecked(new ulong[] + { + 0x0aba_f895_b97e_43c8, + 0xba4c_6432_eb9b_61b0, + 0x1250_6f52_adfe_307f, + 0x7502_8c34_3933_6b72, + 0x8474_4f05_b8e9_bd71, + 0x113d_554f_b095_54f7 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x73e9_0e88_f5cf_01c0, + 0x3700_7b65_dd31_97e2, + 0x5cf9_a199_2f0d_7c78, + 0x4f83_c10b_9eb3_330d, + 0xf6a6_3f6f_07f6_0961, + 0x0c53_b5b9_7e63_4df3 + })); + Assert.IsFalse(a.IsTorsionFree); + + Assert.IsTrue(G1Affine.Identity.IsTorsionFree); + Assert.IsTrue(G1Affine.Generator.IsTorsionFree); + } + + [TestMethod] + public void TestMulByX() + { + // multiplying by `x` a point in G1 is the same as multiplying by + // the equivalent scalar. + var generator = G1Projective.Generator; + var x = BLS_X_IS_NEGATIVE ? -new Scalar(BLS_X) : new Scalar(BLS_X); + Assert.AreEqual(generator.MulByX(), generator * x); + + var point = G1Projective.Generator * new Scalar(42); + Assert.AreEqual(point.MulByX(), point * x); + } + + [TestMethod] + public void TestClearCofactor() + { + // the generator (and the identity) are always on the curve, + // even after clearing the cofactor + var generator = G1Projective.Generator; + Assert.IsTrue(generator.ClearCofactor().IsOnCurve); + var id = G1Projective.Identity; + Assert.IsTrue(id.ClearCofactor().IsOnCurve); + + var z = Fp.FromRawUnchecked(new ulong[] + { + 0x3d2d1c670671394e, + 0x0ee3a800a2f7c1ca, + 0x270f4f21da2e5050, + 0xe02840a53f1be768, + 0x55debeb597512690, + 0x08bd25353dc8f791 + }); + + var point = new G1Projective(Fp.FromRawUnchecked(new ulong[] + { + 0x48af5ff540c817f0, + 0xd73893acaf379d5a, + 0xe6c43584e18e023c, + 0x1eda39c30f188b3e, + 0xf618c6d3ccc0f8d8, + 0x0073542cd671e16c + }) * z, Fp.FromRawUnchecked(new ulong[] + { + 0x57bf8be79461d0ba, + 0xfc61459cee3547c3, + 0x0d23567df1ef147b, + 0x0ee187bcce1d9b64, + 0xb0c8cfbe9dc8fdc1, + 0x1328661767ef368b + }), z.Square() * z); + + Assert.IsTrue(point.IsOnCurve); + Assert.IsFalse(new G1Affine(point).IsTorsionFree); + var cleared_point = point.ClearCofactor(); + Assert.IsTrue(cleared_point.IsOnCurve); + Assert.IsTrue(new G1Affine(cleared_point).IsTorsionFree); + + // in BLS12-381 the cofactor in G1 can be + // cleared multiplying by (1-x) + var h_eff = new Scalar(1) + new Scalar(BLS_X); + Assert.AreEqual(point.ClearCofactor(), point * h_eff); + } + + [TestMethod] + public void TestBatchNormalize() + { + var a = G1Projective.Generator.Double(); + var b = a.Double(); + var c = b.Double(); + + foreach (bool a_identity in new[] { false, true }) + { + foreach (bool b_identity in new[] { false, true }) + { + foreach (bool c_identity in new[] { false, true }) + { + var v = new[] { a, b, c }; + if (a_identity) + { + v[0] = G1Projective.Identity; + } + if (b_identity) + { + v[1] = G1Projective.Identity; + } + if (c_identity) + { + v[2] = G1Projective.Identity; + } + + var t = new G1Affine[3]; + var expected = new[] { new G1Affine(v[0]), new G1Affine(v[1]), new G1Affine(v[2]) }; + + G1Projective.BatchNormalize(v, t); + + CollectionAssert.AreEqual(expected, t); + } + } + } + } +} diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/UT_G2.cs b/tests/Neo.Cryptography.BLS12_381.Tests/UT_G2.cs new file mode 100644 index 0000000000..7b8cadb90e --- /dev/null +++ b/tests/Neo.Cryptography.BLS12_381.Tests/UT_G2.cs @@ -0,0 +1,823 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_G2.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using static Neo.Cryptography.BLS12_381.Constants; +using static Neo.Cryptography.BLS12_381.ConstantTimeUtility; + +namespace Neo.Cryptography.BLS12_381.Tests; + +[TestClass] +public class UT_G2 +{ + [TestMethod] + public void TestIsOnCurve() + { + Assert.IsTrue(G2Affine.Identity.IsOnCurve); + Assert.IsTrue(G2Affine.Generator.IsOnCurve); + Assert.IsTrue(G2Projective.Identity.IsOnCurve); + Assert.IsTrue(G2Projective.Generator.IsOnCurve); + + var z = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e + }), Fp.FromRawUnchecked(new ulong[] + { + 0x1253_25df_3d35_b5a8, + 0xdc46_9ef5_555d_7fe3, + 0x02d7_16d2_4431_06a9, + 0x05a1_db59_a6ff_37d0, + 0x7cf7_784e_5300_bb8f, + 0x16a8_8922_c7a5_e844 + })); + + var gen = G2Affine.Generator; + var test = new G2Projective(gen.X * z, gen.Y * z, z); + + Assert.IsTrue(test.IsOnCurve); + + test = new(in z, in test.Y, in test.Z); + Assert.IsFalse(test.IsOnCurve); + } + + [TestMethod] + public void TestAffinePointEquality() + { + var a = G2Affine.Generator; + var b = G2Affine.Identity; + + Assert.AreEqual(a, a); + Assert.AreEqual(b, b); + Assert.AreNotEqual(a, b); + } + + [TestMethod] + public void TestProjectivePointEquality() + { + var a = G2Projective.Generator; + var b = G2Projective.Identity; + + Assert.AreEqual(a, a); + Assert.AreEqual(b, b); + Assert.AreNotEqual(a, b); + + var z = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e + }), Fp.FromRawUnchecked(new ulong[] + { + 0x1253_25df_3d35_b5a8, + 0xdc46_9ef5_555d_7fe3, + 0x02d7_16d2_4431_06a9, + 0x05a1_db59_a6ff_37d0, + 0x7cf7_784e_5300_bb8f, + 0x16a8_8922_c7a5_e844 + })); + + var c = new G2Projective(a.X * z, a.Y * z, in z); + Assert.IsTrue(c.IsOnCurve); + + Assert.AreEqual(a, c); + Assert.AreNotEqual(b, c); + + c = new(in c.X, -c.Y, in c.Z); + Assert.IsTrue(c.IsOnCurve); + + Assert.AreNotEqual(a, c); + Assert.AreNotEqual(b, c); + + c = new(in z, -c.Y, in c.Z); + Assert.IsFalse(c.IsOnCurve); + Assert.AreNotEqual(a, b); + Assert.AreNotEqual(a, c); + Assert.AreNotEqual(b, c); + } + + [TestMethod] + public void TestConditionallySelectAffine() + { + var a = G2Affine.Generator; + var b = G2Affine.Identity; + + Assert.AreEqual(a, ConditionalSelect(in a, in b, false)); + Assert.AreEqual(b, ConditionalSelect(in a, in b, true)); + } + + [TestMethod] + public void TestConditionallySelectProjective() + { + var a = G2Projective.Generator; + var b = G2Projective.Identity; + + Assert.AreEqual(a, ConditionalSelect(in a, in b, false)); + Assert.AreEqual(b, ConditionalSelect(in a, in b, true)); + } + + [TestMethod] + public void TestProjectiveToAffine() + { + var a = G2Projective.Generator; + var b = G2Projective.Identity; + + Assert.IsTrue(new G2Affine(a).IsOnCurve); + Assert.IsFalse(new G2Affine(a).IsIdentity); + Assert.IsTrue(new G2Affine(b).IsOnCurve); + Assert.IsTrue(new G2Affine(b).IsIdentity); + + var z = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e + }), Fp.FromRawUnchecked(new ulong[] + { + 0x1253_25df_3d35_b5a8, + 0xdc46_9ef5_555d_7fe3, + 0x02d7_16d2_4431_06a9, + 0x05a1_db59_a6ff_37d0, + 0x7cf7_784e_5300_bb8f, + 0x16a8_8922_c7a5_e844 + })); + + var c = new G2Projective(a.X * z, a.Y * z, in z); + + Assert.AreEqual(G2Affine.Generator, new G2Affine(c)); + } + + [TestMethod] + public void TestAffineToProjective() + { + var a = G2Affine.Generator; + var b = G2Affine.Identity; + + Assert.IsTrue(new G2Projective(a).IsOnCurve); + Assert.IsFalse(new G2Projective(a).IsIdentity); + Assert.IsTrue(new G2Projective(b).IsOnCurve); + Assert.IsTrue(new G2Projective(b).IsIdentity); + } + + [TestMethod] + public void TestDoubling() + { + { + var tmp = G2Projective.Identity.Double(); + Assert.IsTrue(tmp.IsIdentity); + Assert.IsTrue(tmp.IsOnCurve); + } + { + var tmp = G2Projective.Generator.Double(); + Assert.IsFalse(tmp.IsIdentity); + Assert.IsTrue(tmp.IsOnCurve); + + Assert.AreEqual(new G2Affine(tmp), new G2Affine(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xe9d9_e2da_9620_f98b, + 0x54f1_1993_46b9_7f36, + 0x3db3_b820_376b_ed27, + 0xcfdb_31c9_b0b6_4f4c, + 0x41d7_c127_8635_4493, + 0x0571_0794_c255_c064 + }), Fp.FromRawUnchecked(new ulong[] + { + 0xd6c1_d3ca_6ea0_d06e, + 0xda0c_bd90_5595_489f, + 0x4f53_52d4_3479_221d, + 0x8ade_5d73_6f8c_97e0, + 0x48cc_8433_925e_f70e, + 0x08d7_ea71_ea91_ef81 + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x15ba_26eb_4b0d_186f, + 0x0d08_6d64_b7e9_e01e, + 0xc8b8_48dd_652f_4c78, + 0xeecf_46a6_123b_ae4f, + 0x255e_8dd8_b6dc_812a, + 0x1641_42af_21dc_f93f + }), Fp.FromRawUnchecked(new ulong[] + { + 0xf9b4_a1a8_9598_4db4, + 0xd417_b114_cccf_f748, + 0x6856_301f_c89f_086e, + 0x41c7_7787_8931_e3da, + 0x3556_b155_066a_2105, + 0x00ac_f7d3_25cb_89cf + })))); + } + } + + [TestMethod] + public void TestProjectiveAddition() + { + { + var a = G2Projective.Identity; + var b = G2Projective.Identity; + var c = a + b; + Assert.IsTrue(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + } + { + var a = G2Projective.Identity; + var b = G2Projective.Generator; + { + var z = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e + }), Fp.FromRawUnchecked(new ulong[] + { + 0x1253_25df_3d35_b5a8, + 0xdc46_9ef5_555d_7fe3, + 0x02d7_16d2_4431_06a9, + 0x05a1_db59_a6ff_37d0, + 0x7cf7_784e_5300_bb8f, + 0x16a8_8922_c7a5_e844 + })); + + b = new G2Projective(b.X * z, b.Y * z, in z); + } + var c = a + b; + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + Assert.AreEqual(G2Projective.Generator, c); + } + { + var a = G2Projective.Identity; + var b = G2Projective.Generator; + { + var z = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e + }), Fp.FromRawUnchecked(new ulong[] + { + 0x1253_25df_3d35_b5a8, + 0xdc46_9ef5_555d_7fe3, + 0x02d7_16d2_4431_06a9, + 0x05a1_db59_a6ff_37d0, + 0x7cf7_784e_5300_bb8f, + 0x16a8_8922_c7a5_e844 + })); + + b = new G2Projective(b.X * z, b.Y * z, in z); + } + var c = b + a; + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + Assert.AreEqual(G2Projective.Generator, c); + } + { + var a = G2Projective.Generator.Double().Double(); // 4P + var b = G2Projective.Generator.Double(); // 2P + var c = a + b; + + var d = G2Projective.Generator; + for (int i = 0; i < 5; i++) + { + d += G2Projective.Generator; + } + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + Assert.IsFalse(d.IsIdentity); + Assert.IsTrue(d.IsOnCurve); + Assert.AreEqual(c, d); + } + + // Degenerate case + { + var beta = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xcd03_c9e4_8671_f071, + 0x5dab_2246_1fcd_a5d2, + 0x5870_42af_d385_1b95, + 0x8eb6_0ebe_01ba_cb9e, + 0x03f9_7d6e_83d0_50d2, + 0x18f0_2065_5463_8741 + }), Fp.Zero); + beta = beta.Square(); + var a = G2Projective.Generator.Double().Double(); + var b = new G2Projective(a.X * beta, -a.Y, in a.Z); + Assert.IsTrue(a.IsOnCurve); + Assert.IsTrue(b.IsOnCurve); + + var c = a + b; + Assert.AreEqual( + new G2Affine(c), + new G2Affine(new G2Projective(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x705a_bc79_9ca7_73d3, + 0xfe13_2292_c1d4_bf08, + 0xf37e_ce3e_07b2_b466, + 0x887e_1c43_f447_e301, + 0x1e09_70d0_33bc_77e8, + 0x1985_c81e_20a6_93f2 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x1d79_b25d_b36a_b924, + 0x2394_8e4d_5296_39d3, + 0x471b_a7fb_0d00_6297, + 0x2c36_d4b4_465d_c4c0, + 0x82bb_c3cf_ec67_f538, + 0x051d_2728_b67b_f952 + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x41b1_bbf6_576c_0abf, + 0xb6cc_9371_3f7a_0f9a, + 0x6b65_b43e_48f3_f01f, + 0xfb7a_4cfc_af81_be4f, + 0x3e32_dadc_6ec2_2cb6, + 0x0bb0_fc49_d798_07e3 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x7d13_9778_8f5f_2ddf, + 0xab29_0714_4ff0_d8e8, + 0x5b75_73e0_cdb9_1f92, + 0x4cb8_932d_d31d_af28, + 0x62bb_fac6_db05_2a54, + 0x11f9_5c16_d14c_3bbe + })), Fp2.One))); + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + } + } + + [TestMethod] + public void TestMixedAddition() + { + { + var a = G2Affine.Identity; + var b = G2Projective.Identity; + var c = a + b; + Assert.IsTrue(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + } + { + var a = G2Affine.Identity; + var b = G2Projective.Generator; + { + var z = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e + }), Fp.FromRawUnchecked(new ulong[] + { + 0x1253_25df_3d35_b5a8, + 0xdc46_9ef5_555d_7fe3, + 0x02d7_16d2_4431_06a9, + 0x05a1_db59_a6ff_37d0, + 0x7cf7_784e_5300_bb8f, + 0x16a8_8922_c7a5_e844 + })); + + b = new G2Projective(b.X * z, b.Y * z, in z); + } + var c = a + b; + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + Assert.AreEqual(G2Projective.Generator, c); + } + { + var a = G2Affine.Identity; + var b = G2Projective.Generator; + { + var z = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xba7a_fa1f_9a6f_e250, + 0xfa0f_5b59_5eaf_e731, + 0x3bdc_4776_94c3_06e7, + 0x2149_be4b_3949_fa24, + 0x64aa_6e06_49b2_078c, + 0x12b1_08ac_3364_3c3e + }), Fp.FromRawUnchecked(new ulong[] + { + 0x1253_25df_3d35_b5a8, + 0xdc46_9ef5_555d_7fe3, + 0x02d7_16d2_4431_06a9, + 0x05a1_db59_a6ff_37d0, + 0x7cf7_784e_5300_bb8f, + 0x16a8_8922_c7a5_e844 + })); + + b = new G2Projective(b.X * z, b.Y * z, in z); + } + var c = b + a; + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + Assert.AreEqual(G2Projective.Generator, c); + } + { + var a = G2Projective.Generator.Double().Double(); // 4P + var b = G2Projective.Generator.Double(); // 2P + var c = a + b; + + var d = G2Projective.Generator; + for (int i = 0; i < 5; i++) + { + d += G2Affine.Generator; + } + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + Assert.IsFalse(d.IsIdentity); + Assert.IsTrue(d.IsOnCurve); + Assert.AreEqual(c, d); + } + + // Degenerate case + { + var beta = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xcd03_c9e4_8671_f071, + 0x5dab_2246_1fcd_a5d2, + 0x5870_42af_d385_1b95, + 0x8eb6_0ebe_01ba_cb9e, + 0x03f9_7d6e_83d0_50d2, + 0x18f0_2065_5463_8741 + }), Fp.Zero); + beta = beta.Square(); + var _a = G2Projective.Generator.Double().Double(); + var b = new G2Projective(_a.X * beta, -_a.Y, in _a.Z); + var a = new G2Affine(_a); + Assert.IsTrue((a.IsOnCurve)); + Assert.IsTrue((b.IsOnCurve)); + + var c = a + b; + Assert.AreEqual(new G2Affine(new G2Projective(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x705a_bc79_9ca7_73d3, + 0xfe13_2292_c1d4_bf08, + 0xf37e_ce3e_07b2_b466, + 0x887e_1c43_f447_e301, + 0x1e09_70d0_33bc_77e8, + 0x1985_c81e_20a6_93f2 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x1d79_b25d_b36a_b924, + 0x2394_8e4d_5296_39d3, + 0x471b_a7fb_0d00_6297, + 0x2c36_d4b4_465d_c4c0, + 0x82bb_c3cf_ec67_f538, + 0x051d_2728_b67b_f952 + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x41b1_bbf6_576c_0abf, + 0xb6cc_9371_3f7a_0f9a, + 0x6b65_b43e_48f3_f01f, + 0xfb7a_4cfc_af81_be4f, + 0x3e32_dadc_6ec2_2cb6, + 0x0bb0_fc49_d798_07e3 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x7d13_9778_8f5f_2ddf, + 0xab29_0714_4ff0_d8e8, + 0x5b75_73e0_cdb9_1f92, + 0x4cb8_932d_d31d_af28, + 0x62bb_fac6_db05_2a54, + 0x11f9_5c16_d14c_3bbe + })), Fp2.One)), new G2Affine(c)); + Assert.IsFalse(c.IsIdentity); + Assert.IsTrue(c.IsOnCurve); + } + } + + [TestMethod] + public void TestProjectiveNegationAndSubtraction() + { + var a = G2Projective.Generator.Double(); + Assert.AreEqual(G2Projective.Identity, a + (-a)); + Assert.AreEqual(a - a, a + (-a)); + } + + [TestMethod] + public void TestAffineNegationAndSubtraction() + { + var a = G2Affine.Generator; + Assert.AreEqual(G2Projective.Identity, new G2Projective(a) + (-a)); + Assert.AreEqual(new G2Projective(a) - a, new G2Projective(a) + (-a)); + } + + [TestMethod] + public void TestProjectiveScalarMultiplication() + { + var g = G2Projective.Generator; + var a = Scalar.FromRaw(new ulong[] + { + 0x2b56_8297_a56d_a71c, + 0xd8c3_9ecb_0ef3_75d1, + 0x435c_38da_67bf_bf96, + 0x8088_a050_26b6_59b2 + }); + var b = Scalar.FromRaw(new ulong[] + { + 0x785f_dd9b_26ef_8b85, + 0xc997_f258_3769_5c18, + 0x4c8d_bc39_e7b7_56c1, + 0x70d9_b6cc_6d87_df20 + }); + var c = a * b; + + Assert.AreEqual(g * c, g * a * b); + } + + [TestMethod] + public void TestAffineScalarMultiplication() + { + var g = G2Affine.Generator; + var a = Scalar.FromRaw(new ulong[] + { + 0x2b56_8297_a56d_a71c, + 0xd8c3_9ecb_0ef3_75d1, + 0x435c_38da_67bf_bf96, + 0x8088_a050_26b6_59b2 + }); + var b = Scalar.FromRaw(new ulong[] + { + 0x785f_dd9b_26ef_8b85, + 0xc997_f258_3769_5c18, + 0x4c8d_bc39_e7b7_56c1, + 0x70d9_b6cc_6d87_df20 + }); + var c = a * b; + + Assert.AreEqual(g * c, new G2Affine(g * a) * b); + } + + [TestMethod] + public void TestIsTorsionFree() + { + var a = new G2Affine(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x89f5_50c8_13db_6431, + 0xa50b_e8c4_56cd_8a1a, + 0xa45b_3741_14ca_e851, + 0xbb61_90f5_bf7f_ff63, + 0x970c_a02c_3ba8_0bc7, + 0x02b8_5d24_e840_fbac + }), + Fp.FromRawUnchecked(new ulong[] + { + 0x6888_bc53_d707_16dc, + 0x3dea_6b41_1768_2d70, + 0xd8f5_f930_500c_a354, + 0x6b5e_cb65_56f5_c155, + 0xc96b_ef04_3477_8ab0, + 0x0508_1505_5150_06ad + })), new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x3cf1_ea0d_434b_0f40, + 0x1a0d_c610_e603_e333, + 0x7f89_9561_60c7_2fa0, + 0x25ee_03de_cf64_31c5, + 0xeee8_e206_ec0f_e137, + 0x0975_92b2_26df_ef28 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x71e8_bb5f_2924_7367, + 0xa5fe_049e_2118_31ce, + 0x0ce6_b354_502a_3896, + 0x93b0_1200_0997_314e, + 0x6759_f3b6_aa5b_42ac, + 0x1569_44c4_dfe9_2bbb + }))); + Assert.IsFalse(a.IsTorsionFree); + + Assert.IsTrue(G2Affine.Identity.IsTorsionFree); + Assert.IsTrue(G2Affine.Generator.IsTorsionFree); + } + + [TestMethod] + public void TestMulByX() + { + // multiplying by `x` a point in G2 is the same as multiplying by + // the equivalent scalar. + var generator = G2Projective.Generator; + var x = BLS_X_IS_NEGATIVE ? -new Scalar(BLS_X) : new Scalar(BLS_X); + Assert.AreEqual(generator * x, generator.MulByX()); + + var point = G2Projective.Generator * new Scalar(42); + Assert.AreEqual(point * x, point.MulByX()); + } + + [TestMethod] + public void TestPsi() + { + var generator = G2Projective.Generator; + + var z = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x0ef2ddffab187c0a, + 0x2424522b7d5ecbfc, + 0xc6f341a3398054f4, + 0x5523ddf409502df0, + 0xd55c0b5a88e0dd97, + 0x066428d704923e52 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x538bbe0c95b4878d, + 0xad04a50379522881, + 0x6d5c05bf5c12fb64, + 0x4ce4a069a2d34787, + 0x59ea6c8d0dffaeaf, + 0x0d42a083a75bd6f3 + })); + + // `point` is a random point in the curve + var point = new G2Projective(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xee4c8cb7c047eaf2, + 0x44ca22eee036b604, + 0x33b3affb2aefe101, + 0x15d3e45bbafaeb02, + 0x7bfc2154cd7419a4, + 0x0a2d0c2b756e5edc + }), Fp.FromRawUnchecked(new ulong[] + { + 0xfc224361029a8777, + 0x4cbf2baab8740924, + 0xc5008c6ec6592c89, + 0xecc2c57b472a9c2d, + 0x8613eafd9d81ffb1, + 0x10fe54daa2d3d495 + })) * z, new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x7de7edc43953b75c, + 0x58be1d2de35e87dc, + 0x5731d30b0e337b40, + 0xbe93b60cfeaae4c9, + 0x8b22c203764bedca, + 0x01616c8d1033b771 + }), Fp.FromRawUnchecked(new ulong[] + { + 0xea126fe476b5733b, + 0x85cee68b5dae1652, + 0x98247779f7272b04, + 0xa649c8b468c6e808, + 0xb5b9a62dff0c4e45, + 0x1555b67fc7bbe73d + })), z.Square() * z); + Assert.IsTrue(point.IsOnCurve); + + // psi2(P) = psi(psi(P)) + Assert.AreEqual(generator.Psi2(), generator.Psi().Psi()); + Assert.AreEqual(point.Psi2(), point.Psi().Psi()); + // psi(P) is a morphism + Assert.AreEqual(generator.Double().Psi(), generator.Psi().Double()); + Assert.AreEqual(point.Psi() + generator.Psi(), (point + generator).Psi()); + // psi(P) behaves in the same way on the same projective point + var normalized_points = new G2Affine[1]; + G2Projective.BatchNormalize(new[] { point }, normalized_points); + var normalized_point = new G2Projective(normalized_points[0]); + Assert.AreEqual(point.Psi(), normalized_point.Psi()); + Assert.AreEqual(point.Psi2(), normalized_point.Psi2()); + } + + [TestMethod] + public void TestClearCofactor() + { + var z = new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x0ef2ddffab187c0a, + 0x2424522b7d5ecbfc, + 0xc6f341a3398054f4, + 0x5523ddf409502df0, + 0xd55c0b5a88e0dd97, + 0x066428d704923e52 + }), Fp.FromRawUnchecked(new ulong[] + { + 0x538bbe0c95b4878d, + 0xad04a50379522881, + 0x6d5c05bf5c12fb64, + 0x4ce4a069a2d34787, + 0x59ea6c8d0dffaeaf, + 0x0d42a083a75bd6f3 + })); + + // `point` is a random point in the curve + var point = new G2Projective(new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0xee4c8cb7c047eaf2, + 0x44ca22eee036b604, + 0x33b3affb2aefe101, + 0x15d3e45bbafaeb02, + 0x7bfc2154cd7419a4, + 0x0a2d0c2b756e5edc + }), Fp.FromRawUnchecked(new ulong[] + { + 0xfc224361029a8777, + 0x4cbf2baab8740924, + 0xc5008c6ec6592c89, + 0xecc2c57b472a9c2d, + 0x8613eafd9d81ffb1, + 0x10fe54daa2d3d495 + })) * z, new Fp2(Fp.FromRawUnchecked(new ulong[] + { + 0x7de7edc43953b75c, + 0x58be1d2de35e87dc, + 0x5731d30b0e337b40, + 0xbe93b60cfeaae4c9, + 0x8b22c203764bedca, + 0x01616c8d1033b771 + }), Fp.FromRawUnchecked(new ulong[] + { + 0xea126fe476b5733b, + 0x85cee68b5dae1652, + 0x98247779f7272b04, + 0xa649c8b468c6e808, + 0xb5b9a62dff0c4e45, + 0x1555b67fc7bbe73d + })), z.Square() * z); + + Assert.IsTrue(point.IsOnCurve); + Assert.IsFalse(new G2Affine(point).IsTorsionFree); + var cleared_point = point.ClearCofactor(); + + Assert.IsTrue(cleared_point.IsOnCurve); + Assert.IsTrue(new G2Affine(cleared_point).IsTorsionFree); + + // the generator (and the identity) are always on the curve, + // even after clearing the cofactor + var generator = G2Projective.Generator; + Assert.IsTrue(generator.ClearCofactor().IsOnCurve); + var id = G2Projective.Identity; + Assert.IsTrue(id.ClearCofactor().IsOnCurve); + + // test the effect on q-torsion points multiplying by h_eff modulo |Scalar| + // h_eff % q = 0x2b116900400069009a40200040001ffff + byte[] h_eff_modq = + { + 0xff, 0xff, 0x01, 0x00, 0x04, 0x00, 0x02, 0xa4, 0x09, 0x90, 0x06, 0x00, 0x04, 0x90, 0x16, + 0xb1, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00 + }; + Assert.AreEqual(generator * h_eff_modq, generator.ClearCofactor()); + Assert.AreEqual(cleared_point * h_eff_modq, cleared_point.ClearCofactor()); + } + + [TestMethod] + public void TestBatchNormalize() + { + var a = G2Projective.Generator.Double(); + var b = a.Double(); + var c = b.Double(); + + foreach (bool a_identity in new[] { false, true }) + { + foreach (bool b_identity in new[] { false, true }) + { + foreach (bool c_identity in new[] { false, true }) + { + var v = new[] { a, b, c }; + if (a_identity) + { + v[0] = G2Projective.Identity; + } + if (b_identity) + { + v[1] = G2Projective.Identity; + } + if (c_identity) + { + v[2] = G2Projective.Identity; + } + + var t = new G2Affine[3]; + var expected = new[] { new G2Affine(v[0]), new G2Affine(v[1]), new G2Affine(v[2]) }; + + G2Projective.BatchNormalize(v, t); + + CollectionAssert.AreEqual(t, expected); + } + } + } + } +} diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/UT_Pairings.cs b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Pairings.cs new file mode 100644 index 0000000000..9019477a8f --- /dev/null +++ b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Pairings.cs @@ -0,0 +1,69 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Pairings.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.Cryptography.BLS12_381.Tests; + +[TestClass] +public class UT_Pairings +{ + [TestMethod] + public void TestGtGenerator() + { + Assert.AreEqual( + Gt.Generator, + Bls12.Pairing(in G1Affine.Generator, in G2Affine.Generator) + ); + } + + [TestMethod] + public void TestBilinearity() + { + var a = Scalar.FromRaw(new ulong[] { 1, 2, 3, 4 }).Invert().Square(); + var b = Scalar.FromRaw(new ulong[] { 5, 6, 7, 8 }).Invert().Square(); + var c = a * b; + + var g = new G1Affine(G1Affine.Generator * a); + var h = new G2Affine(G2Affine.Generator * b); + var p = Bls12.Pairing(in g, in h); + + Assert.AreNotEqual(Gt.Identity, p); + + var expected = new G1Affine(G1Affine.Generator * c); + + Assert.AreEqual(p, Bls12.Pairing(in expected, in G2Affine.Generator)); + Assert.AreEqual( + p, + Bls12.Pairing(in G1Affine.Generator, in G2Affine.Generator) * c + ); + } + + [TestMethod] + public void TestUnitary() + { + var g = G1Affine.Generator; + var h = G2Affine.Generator; + var p = -Bls12.Pairing(in g, in h); + var q = Bls12.Pairing(in g, -h); + var r = Bls12.Pairing(-g, in h); + + Assert.AreEqual(p, q); + Assert.AreEqual(q, r); + } + + [TestMethod] + public void TestMillerLoopResultDefault() + { + Assert.AreEqual( + Gt.Identity, + new MillerLoopResult(Fp12.One).FinalExponentiation() + ); + } +} diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/UT_Scalar.cs b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Scalar.cs new file mode 100644 index 0000000000..03c1526a7b --- /dev/null +++ b/tests/Neo.Cryptography.BLS12_381.Tests/UT_Scalar.cs @@ -0,0 +1,411 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Scalar.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using static Neo.Cryptography.BLS12_381.ScalarConstants; + +namespace Neo.Cryptography.BLS12_381.Tests; + +[TestClass] +public class UT_Scalar +{ + private static readonly Scalar LARGEST = new(new ulong[] + { + 0xffff_ffff_0000_0000, + 0x53bd_a402_fffe_5bfe, + 0x3339_d808_09a1_d805, + 0x73ed_a753_299d_7d48 + }); + + [TestMethod] + public void TestInv() + { + // Compute -(q^{-1} mod 2^64) mod 2^64 by exponentiating + // by totient(2**64) - 1 + + var inv = 1ul; + for (int i = 0; i < 63; i++) + { + inv = unchecked(inv * inv); + inv = unchecked(inv * MODULUS_LIMBS_64[0]); + } + inv = unchecked(~inv + 1); + + Assert.AreEqual(INV, inv); + } + + [TestMethod] + public void TestToString() + { + Assert.AreEqual("0x0000000000000000000000000000000000000000000000000000000000000000", Scalar.Zero.ToString()); + Assert.AreEqual("0x0000000000000000000000000000000000000000000000000000000000000001", Scalar.One.ToString()); + Assert.AreEqual("0x1824b159acc5056f998c4fefecbc4ff55884b7fa0003480200000001fffffffe", R2.ToString()); + } + + [TestMethod] + public void TestEquality() + { + Assert.AreEqual(Scalar.Zero, Scalar.Zero); + Assert.AreEqual(Scalar.One, Scalar.One); + Assert.AreEqual(R2, R2); + + Assert.AreNotEqual(Scalar.Zero, Scalar.One); + Assert.AreNotEqual(Scalar.One, R2); + } + + [TestMethod] + public void TestToBytes() + { + CollectionAssert.AreEqual(new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0 + }, Scalar.Zero.ToArray()); + + CollectionAssert.AreEqual(new byte[] + { + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0 + }, Scalar.One.ToArray()); + + CollectionAssert.AreEqual(new byte[] + { + 254, 255, 255, 255, 1, 0, 0, 0, 2, 72, 3, 0, 250, 183, 132, 88, 245, 79, 188, 236, 239, + 79, 140, 153, 111, 5, 197, 172, 89, 177, 36, 24 + }, R2.ToArray()); + + CollectionAssert.AreEqual(new byte[] + { + 0, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8, + 216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 115 + }, (-Scalar.One).ToArray()); + } + + [TestMethod] + public void TestFromBytes() + { + Assert.AreEqual(Scalar.Zero, Scalar.FromBytes(new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0 + })); + + Assert.AreEqual(Scalar.One, Scalar.FromBytes(new byte[] + { + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0 + })); + + Assert.AreEqual(R2, Scalar.FromBytes(new byte[] + { + 254, 255, 255, 255, 1, 0, 0, 0, 2, 72, 3, 0, 250, 183, 132, 88, 245, 79, 188, 236, 239, + 79, 140, 153, 111, 5, 197, 172, 89, 177, 36, 24 + })); + + // -1 should work + Scalar.FromBytes(new byte[] + { + 0, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8, + 216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 115 + }); + + // modulus is invalid + Assert.ThrowsException(() => Scalar.FromBytes(new byte[] + { + 1, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8, + 216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 115 + })); + + // Anything larger than the modulus is invalid + Assert.ThrowsException(() => Scalar.FromBytes(new byte[] + { + 2, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8, + 216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 115 + })); + Assert.ThrowsException(() => Scalar.FromBytes(new byte[] + { + 1, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8, + 216, 58, 51, 72, 125, 157, 41, 83, 167, 237, 115 + })); + Assert.ThrowsException(() => Scalar.FromBytes(new byte[] + { + 1, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8, + 216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 116 + })); + } + + [TestMethod] + public void TestFromBytesWideR2() + { + Assert.AreEqual(R2, Scalar.FromBytesWide(new byte[] + { + 254, 255, 255, 255, 1, 0, 0, 0, 2, 72, 3, 0, 250, 183, 132, 88, 245, 79, 188, 236, 239, + 79, 140, 153, 111, 5, 197, 172, 89, 177, 36, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + })); + } + + [TestMethod] + public void TestFromBytesWideNegativeOne() + { + Assert.AreEqual(-Scalar.One, Scalar.FromBytesWide(new byte[] + { + 0, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8, + 216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 115, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + })); + } + + [TestMethod] + public void TestFromBytesWideMaximum() + { + Assert.AreEqual(new Scalar(new ulong[] + { + 0xc62c_1805_439b_73b1, + 0xc2b9_551e_8ced_218e, + 0xda44_ec81_daf9_a422, + 0x5605_aa60_1c16_2e79 + }), Scalar.FromBytesWide(Enumerable.Repeat(0xff, 64).ToArray())); + } + + [TestMethod] + public void TestZero() + { + Assert.AreEqual(Scalar.Zero, -Scalar.Zero); + Assert.AreEqual(Scalar.Zero, Scalar.Zero + Scalar.Zero); + Assert.AreEqual(Scalar.Zero, Scalar.Zero - Scalar.Zero); + Assert.AreEqual(Scalar.Zero, Scalar.Zero * Scalar.Zero); + } + + [TestMethod] + public void TestAddition() + { + var tmp = LARGEST; + tmp += LARGEST; + + Assert.AreEqual(new Scalar(new ulong[] + { + 0xffff_fffe_ffff_ffff, + 0x53bd_a402_fffe_5bfe, + 0x3339_d808_09a1_d805, + 0x73ed_a753_299d_7d48 + }), tmp); + + tmp = LARGEST; + tmp += new Scalar(new ulong[] { 1, 0, 0, 0 }); + + Assert.AreEqual(Scalar.Zero, tmp); + } + + [TestMethod] + public void TestNegation() + { + var tmp = -LARGEST; + + Assert.AreEqual(new Scalar(new ulong[] { 1, 0, 0, 0 }), tmp); + + tmp = -Scalar.Zero; + Assert.AreEqual(Scalar.Zero, tmp); + tmp = -new Scalar(new ulong[] { 1, 0, 0, 0 }); + Assert.AreEqual(LARGEST, tmp); + } + + [TestMethod] + public void TestSubtraction() + { + var tmp = LARGEST; + tmp -= LARGEST; + + Assert.AreEqual(Scalar.Zero, tmp); + + tmp = Scalar.Zero; + tmp -= LARGEST; + + var tmp2 = MODULUS; + tmp2 -= LARGEST; + + Assert.AreEqual(tmp, tmp2); + } + + [TestMethod] + public void TestMultiplication() + { + var cur = LARGEST; + + for (int i = 0; i < 100; i++) + { + var tmp = cur; + tmp *= cur; + + var tmp2 = Scalar.Zero; + foreach (bool b in cur + .ToArray() + .SelectMany(p => Enumerable.Range(0, 8).Select(q => ((p >> q) & 1) == 1)) + .Reverse()) + { + var tmp3 = tmp2; + tmp2 += tmp3; + + if (b) + { + tmp2 += cur; + } + } + + Assert.AreEqual(tmp, tmp2); + + cur += LARGEST; + } + } + + [TestMethod] + public void TestSquaring() + { + var cur = LARGEST; + + for (int i = 0; i < 100; i++) + { + var tmp = cur; + tmp = tmp.Square(); + + var tmp2 = Scalar.Zero; + foreach (bool b in cur + .ToArray() + .SelectMany(p => Enumerable.Range(0, 8).Select(q => ((p >> q) & 1) == 1)) + .Reverse()) + { + var tmp3 = tmp2; + tmp2 += tmp3; + + if (b) + { + tmp2 += cur; + } + } + + Assert.AreEqual(tmp, tmp2); + + cur += LARGEST; + } + } + + [TestMethod] + public void TestInversion() + { + Assert.ThrowsException(() => Scalar.Zero.Invert()); + Assert.AreEqual(Scalar.One, Scalar.One.Invert()); + Assert.AreEqual(-Scalar.One, (-Scalar.One).Invert()); + + var tmp = R2; + + for (int i = 0; i < 100; i++) + { + var tmp2 = tmp.Invert(); + tmp2 *= tmp; + + Assert.AreEqual(Scalar.One, tmp2); + + tmp += R2; + } + } + + [TestMethod] + public void TestInvertIsPow() + { + ulong[] q_minus_2 = + { + 0xffff_fffe_ffff_ffff, + 0x53bd_a402_fffe_5bfe, + 0x3339_d808_09a1_d805, + 0x73ed_a753_299d_7d48 + }; + + var r1 = R; + var r2 = R; + var r3 = R; + + for (int i = 0; i < 100; i++) + { + r1 = r1.Invert(); + r2 = r2.PowVartime(q_minus_2); + r3 = r3.Pow(q_minus_2); + + Assert.AreEqual(r1, r2); + Assert.AreEqual(r2, r3); + // Add R so we check something different next time around + r1 += R; + r2 = r1; + r3 = r1; + } + } + + [TestMethod] + public void TestSqrt() + { + Assert.AreEqual(Scalar.Zero.Sqrt(), Scalar.Zero); + + var square = new Scalar(new ulong[] + { + 0x46cd_85a5_f273_077e, + 0x1d30_c47d_d68f_c735, + 0x77f6_56f6_0bec_a0eb, + 0x494a_a01b_df32_468d + }); + + var none_count = 0; + + for (int i = 0; i < 100; i++) + { + Scalar square_root; + try + { + square_root = square.Sqrt(); + Assert.AreEqual(square, square_root * square_root); + } + catch (ArithmeticException) + { + none_count++; + } + square -= Scalar.One; + } + + Assert.AreEqual(49, none_count); + } + + [TestMethod] + public void TestFromRaw() + { + Assert.AreEqual(Scalar.FromRaw(new ulong[] + { + 0x0001_ffff_fffd, + 0x5884_b7fa_0003_4802, + 0x998c_4fef_ecbc_4ff5, + 0x1824_b159_acc5_056f + }), Scalar.FromRaw(Enumerable.Repeat(0xffff_ffff_ffff_ffff, 4).ToArray())); + + Assert.AreEqual(Scalar.Zero, Scalar.FromRaw(MODULUS_LIMBS_64)); + + Assert.AreEqual(R, Scalar.FromRaw(new ulong[] { 1, 0, 0, 0 })); + } + + [TestMethod] + public void TestDouble() + { + var a = Scalar.FromRaw(new ulong[] + { + 0x1fff_3231_233f_fffd, + 0x4884_b7fa_0003_4802, + 0x998c_4fef_ecbc_4ff3, + 0x1824_b159_acc5_0562 + }); + + Assert.AreEqual(a + a, a.Double()); + } +} diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/Usings.cs b/tests/Neo.Cryptography.BLS12_381.Tests/Usings.cs new file mode 100644 index 0000000000..d35553e8ae --- /dev/null +++ b/tests/Neo.Cryptography.BLS12_381.Tests/Usings.cs @@ -0,0 +1,12 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Usings.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +global using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/Helper.cs b/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/Helper.cs new file mode 100644 index 0000000000..15f796046b --- /dev/null +++ b/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/Helper.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Helper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.IO; + +namespace Neo.Cryptography.MPTTrie.Tests +{ + public static class Helper + { + private static readonly byte Prefix = 0xf0; + + public static byte[] ToKey(this UInt256 hash) + { + byte[] buffer = new byte[UInt256.Length + 1]; + using (MemoryStream ms = new MemoryStream(buffer, true)) + using (BinaryWriter writer = new BinaryWriter(ms)) + { + writer.Write(Prefix); + hash.Serialize(writer); + } + return buffer; + } + } +} diff --git a/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Cache.cs b/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Cache.cs new file mode 100644 index 0000000000..3a34962059 --- /dev/null +++ b/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Cache.cs @@ -0,0 +1,235 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Cache.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO; +using Neo.Persistence; +using System.Text; + +namespace Neo.Cryptography.MPTTrie.Tests +{ + + [TestClass] + public class UT_Cache + { + private readonly byte Prefix = 0xf0; + + [TestMethod] + public void TestResolveLeaf() + { + var n = Node.NewLeaf(Encoding.ASCII.GetBytes("leaf")); + var store = new MemoryStore(); + store.Put(n.Hash.ToKey(), n.ToArray()); + var snapshot = store.GetSnapshot(); + var cache = new Cache(snapshot, Prefix); + var resolved = cache.Resolve(n.Hash); + Assert.AreEqual(n.Hash, resolved.Hash); + Assert.AreEqual(n.Value.Span.ToHexString(), resolved.Value.Span.ToHexString()); + } + + [TestMethod] + public void TestResolveBranch() + { + var l = Node.NewLeaf(Encoding.ASCII.GetBytes("leaf")); + var b = Node.NewBranch(); + b.Children[1] = l; + var store = new MemoryStore(); + store.Put(b.Hash.ToKey(), b.ToArray()); + store.Put(l.Hash.ToKey(), l.ToArray()); + var snapshot = store.GetSnapshot(); + var cache = new Cache(snapshot, Prefix); + var resolved_b = cache.Resolve(b.Hash); + Assert.AreEqual(b.Hash, resolved_b.Hash); + Assert.AreEqual(l.Hash, resolved_b.Children[1].Hash); + var resolved_l = cache.Resolve(l.Hash); + Assert.AreEqual(l.Value.Span.ToHexString(), resolved_l.Value.Span.ToHexString()); + } + + [TestMethod] + public void TestResolveExtension() + { + var e = Node.NewExtension(new byte[] { 0x01 }, new Node()); + var store = new MemoryStore(); + store.Put(e.Hash.ToKey(), e.ToArray()); + var snapshot = store.GetSnapshot(); + var cache = new Cache(snapshot, Prefix); + var re = cache.Resolve(e.Hash); + Assert.AreEqual(e.Hash, re.Hash); + Assert.AreEqual(e.Key.Span.ToHexString(), re.Key.Span.ToHexString()); + Assert.IsTrue(re.Next.IsEmpty); + } + + [TestMethod] + public void TestGetAndChangedBranch() + { + var l = Node.NewLeaf(Encoding.ASCII.GetBytes("leaf")); + var b = Node.NewBranch(); + var store = new MemoryStore(); + store.Put(b.Hash.ToKey(), b.ToArray()); + var snapshot = store.GetSnapshot(); + var cache = new Cache(snapshot, Prefix); + var resolved_b = cache.Resolve(b.Hash); + Assert.AreEqual(resolved_b.Hash, b.Hash); + foreach (var n in resolved_b.Children) + { + Assert.IsTrue(n.IsEmpty); + } + resolved_b.Children[1] = l; + resolved_b.SetDirty(); + var resovled_b1 = cache.Resolve(b.Hash); + Assert.AreEqual(resovled_b1.Hash, b.Hash); + foreach (var n in resovled_b1.Children) + { + Assert.IsTrue(n.IsEmpty); + } + } + + [TestMethod] + public void TestGetAndChangedExtension() + { + var e = Node.NewExtension(new byte[] { 0x01 }, new Node()); + var store = new MemoryStore(); + store.Put(e.Hash.ToKey(), e.ToArray()); + var snapshot = store.GetSnapshot(); + var cache = new Cache(snapshot, Prefix); + var re = cache.Resolve(e.Hash); + Assert.AreEqual(e.Hash, re.Hash); + Assert.AreEqual(e.Key.Span.ToHexString(), re.Key.Span.ToHexString()); + Assert.IsTrue(re.Next.IsEmpty); + re.Key = new byte[] { 0x02 }; + re.SetDirty(); + var re1 = cache.Resolve(e.Hash); + Assert.AreEqual(e.Hash, re1.Hash); + Assert.AreEqual(e.Key.Span.ToHexString(), re1.Key.Span.ToHexString()); + Assert.IsTrue(re1.Next.IsEmpty); + } + + [TestMethod] + public void TestGetAndChangedLeaf() + { + var l = Node.NewLeaf(Encoding.ASCII.GetBytes("leaf")); + var store = new MemoryStore(); + store.Put(l.Hash.ToKey(), l.ToArray()); + var snapshot = store.GetSnapshot(); + var cache = new Cache(snapshot, Prefix); + var rl = cache.Resolve(l.Hash); + Assert.AreEqual(l.Hash, rl.Hash); + Assert.AreEqual("leaf", Encoding.ASCII.GetString(rl.Value.Span)); + rl.Value = new byte[] { 0x01 }; + rl.SetDirty(); + var rl1 = cache.Resolve(l.Hash); + Assert.AreEqual(l.Hash, rl1.Hash); + Assert.AreEqual("leaf", Encoding.ASCII.GetString(rl1.Value.Span)); + } + + [TestMethod] + public void TestPutAndChangedBranch() + { + var l = Node.NewLeaf(Encoding.ASCII.GetBytes("leaf")); + var b = Node.NewBranch(); + var h = b.Hash; + var store = new MemoryStore(); + var snapshot = store.GetSnapshot(); + var cache = new Cache(snapshot, Prefix); + cache.PutNode(b); + var rb = cache.Resolve(h); + Assert.AreEqual(h, rb.Hash); + foreach (var n in rb.Children) + { + Assert.IsTrue(n.IsEmpty); + } + rb.Children[1] = l; + rb.SetDirty(); + var rb1 = cache.Resolve(h); + Assert.AreEqual(h, rb1.Hash); + foreach (var n in rb1.Children) + { + Assert.IsTrue(n.IsEmpty); + } + } + + [TestMethod] + public void TestPutAndChangedExtension() + { + var e = Node.NewExtension(new byte[] { 0x01 }, new Node()); + var h = e.Hash; + var store = new MemoryStore(); + var snapshot = store.GetSnapshot(); + var cache = new Cache(snapshot, Prefix); + cache.PutNode(e); + var re = cache.Resolve(e.Hash); + Assert.AreEqual(e.Hash, re.Hash); + Assert.AreEqual(e.Key.Span.ToHexString(), re.Key.Span.ToHexString()); + Assert.IsTrue(re.Next.IsEmpty); + e.Key = new byte[] { 0x02 }; + e.Next = e; + e.SetDirty(); + var re1 = cache.Resolve(h); + Assert.AreEqual(h, re1.Hash); + Assert.AreEqual("01", re1.Key.Span.ToHexString()); + Assert.IsTrue(re1.Next.IsEmpty); + } + + [TestMethod] + public void TestPutAndChangedLeaf() + { + var l = Node.NewLeaf(Encoding.ASCII.GetBytes("leaf")); + var h = l.Hash; + var store = new MemoryStore(); + var snapshot = store.GetSnapshot(); + var cache = new Cache(snapshot, Prefix); + cache.PutNode(l); + var rl = cache.Resolve(l.Hash); + Assert.AreEqual(h, rl.Hash); + Assert.AreEqual("leaf", Encoding.ASCII.GetString(rl.Value.Span)); + l.Value = new byte[] { 0x01 }; + l.SetDirty(); + var rl1 = cache.Resolve(h); + Assert.AreEqual(h, rl1.Hash); + Assert.AreEqual("leaf", Encoding.ASCII.GetString(rl1.Value.Span)); + } + + [TestMethod] + public void TestReference1() + { + var l = Node.NewLeaf(Encoding.ASCII.GetBytes("leaf")); + var store = new MemoryStore(); + var snapshot = store.GetSnapshot(); + var cache = new Cache(snapshot, Prefix); + cache.PutNode(l); + cache.Commit(); + snapshot.Commit(); + var snapshot1 = store.GetSnapshot(); + var cache1 = new Cache(snapshot1, Prefix); + cache1.PutNode(l); + cache1.Commit(); + snapshot1.Commit(); + var snapshot2 = store.GetSnapshot(); + var cache2 = new Cache(snapshot2, Prefix); + var rl = cache2.Resolve(l.Hash); + Assert.AreEqual(2, rl.Reference); + } + + [TestMethod] + public void TestReference2() + { + var l = Node.NewLeaf(Encoding.ASCII.GetBytes("leaf")); + var store = new MemoryStore(); + var snapshot = store.GetSnapshot(); + var cache = new Cache(snapshot, Prefix); + cache.PutNode(l); + cache.PutNode(l); + cache.DeleteNode(l.Hash); + var rl = cache.Resolve(l.Hash); + Assert.AreEqual(1, rl.Reference); + } + } +} diff --git a/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Helper.cs b/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Helper.cs new file mode 100644 index 0000000000..980e7a429e --- /dev/null +++ b/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Helper.cs @@ -0,0 +1,37 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Helper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; + +namespace Neo.Cryptography.MPTTrie.Tests +{ + [TestClass] + public class UT_Helper + { + [TestMethod] + public void TestCompareTo() + { + ReadOnlySpan arr1 = new byte[] { 0, 1, 2 }; + ReadOnlySpan arr2 = new byte[] { 0, 1, 2 }; + Assert.AreEqual(0, arr1.CompareTo(arr2)); + arr1 = new byte[] { 0, 1 }; + Assert.AreEqual(-1, arr1.CompareTo(arr2)); + arr2 = new byte[] { 0 }; + Assert.AreEqual(1, arr1.CompareTo(arr2)); + arr2 = new byte[] { 0, 2 }; + Assert.AreEqual(-1, arr1.CompareTo(arr2)); + arr1 = new byte[] { 0, 3, 1 }; + Assert.AreEqual(1, arr1.CompareTo(arr2)); + Assert.AreEqual(0, ReadOnlySpan.Empty.CompareTo(ReadOnlySpan.Empty)); + } + } +} diff --git a/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Node.cs b/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Node.cs new file mode 100644 index 0000000000..8f46fbec27 --- /dev/null +++ b/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Node.cs @@ -0,0 +1,216 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Node.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace Neo.Cryptography.MPTTrie.Tests +{ + + [TestClass] + public class UT_Node + { + private byte[] NodeToArrayAsChild(Node n) + { + using var ms = new MemoryStream(); + using var writer = new BinaryWriter(ms, Neo.Utility.StrictUTF8, true); + + n.SerializeAsChild(writer); + writer.Flush(); + return ms.ToArray(); + } + + [TestMethod] + public void TestHashSerialize() + { + var n = Node.NewHash(UInt256.Zero); + var expect = "030000000000000000000000000000000000000000000000000000000000000000"; + Assert.AreEqual(expect, n.ToArray().ToHexString()); + Assert.AreEqual(expect, NodeToArrayAsChild(n).ToHexString()); + } + + [TestMethod] + public void TestEmptySerialize() + { + var n = new Node(); + var expect = "04"; + Assert.AreEqual(expect, n.ToArray().ToHexString()); + Assert.AreEqual(expect, NodeToArrayAsChild(n).ToHexString()); + } + + [TestMethod] + public void TestLeafSerialize() + { + var n = Node.NewLeaf(Encoding.ASCII.GetBytes("leaf")); + var expect = "02" + "04" + Encoding.ASCII.GetBytes("leaf").ToHexString(); + Assert.AreEqual(expect, n.ToArrayWithoutReference().ToHexString()); + expect += "01"; + Assert.AreEqual(expect, n.ToArray().ToHexString()); + Assert.AreEqual(7, n.Size); + } + + [TestMethod] + public void TestLeafSerializeAsChild() + { + var l = Node.NewLeaf(Encoding.ASCII.GetBytes("leaf")); + var expect = "03" + Crypto.Hash256(new byte[] { 0x02, 0x04 }.Concat(Encoding.ASCII.GetBytes("leaf")).ToArray()).ToHexString(); + Assert.AreEqual(expect, NodeToArrayAsChild(l).ToHexString()); + } + + [TestMethod] + public void TestExtensionSerialize() + { + var e = Node.NewExtension("010a".HexToBytes(), new Node()); + var expect = "01" + "02" + "010a" + "04"; + Assert.AreEqual(expect, e.ToArrayWithoutReference().ToHexString()); + expect += "01"; + Assert.AreEqual(expect, e.ToArray().ToHexString()); + Assert.AreEqual(6, e.Size); + } + + [TestMethod] + public void TestExtensionSerializeAsChild() + { + var e = Node.NewExtension("010a".HexToBytes(), new Node()); + var expect = "03" + Crypto.Hash256(new byte[] { 0x01, 0x02, 0x01, 0x0a, 0x04 + }).ToHexString(); + Assert.AreEqual(expect, NodeToArrayAsChild(e).ToHexString()); + } + + [TestMethod] + public void TestBranchSerialize() + { + var n = Node.NewBranch(); + n.Children[1] = Node.NewLeaf(Encoding.ASCII.GetBytes("leaf1")); + n.Children[10] = Node.NewLeaf(Encoding.ASCII.GetBytes("leafa")); + var expect = "00"; + for (int i = 0; i < Node.BranchChildCount; i++) + { + if (i == 1) + expect += "03" + Crypto.Hash256(new byte[] { 0x02, 0x05 }.Concat(Encoding.ASCII.GetBytes("leaf1")).ToArray()).ToHexString(); + else if (i == 10) + expect += "03" + Crypto.Hash256(new byte[] { 0x02, 0x05 }.Concat(Encoding.ASCII.GetBytes("leafa")).ToArray()).ToHexString(); + else + expect += "04"; + } + expect += "01"; + Assert.AreEqual(expect, n.ToArray().ToHexString()); + Assert.AreEqual(83, n.Size); + } + + [TestMethod] + public void TestBranchSerializeAsChild() + { + var n = Node.NewBranch(); + var data = new List(); + data.Add(0x00); + for (int i = 0; i < Node.BranchChildCount; i++) + { + data.Add(0x04); + } + var expect = "03" + Crypto.Hash256(data.ToArray()).ToHexString(); + Assert.AreEqual(expect, NodeToArrayAsChild(n).ToHexString()); + } + + [TestMethod] + public void TestCloneBranch() + { + var l = Node.NewLeaf(Encoding.ASCII.GetBytes("leaf")); + var n = Node.NewBranch(); + var n1 = n.Clone(); + n1.Children[0] = l; + Assert.IsTrue(n.Children[0].IsEmpty); + } + + [TestMethod] + public void TestCloneExtension() + { + var l = Node.NewLeaf(Encoding.ASCII.GetBytes("leaf")); + var n = Node.NewExtension(new byte[] { 0x01 }, new Node()); + var n1 = n.Clone(); + n1.Next = l; + Assert.IsTrue(n.Next.IsEmpty); + } + + [TestMethod] + public void TestCloneLeaf() + { + var l = Node.NewLeaf(Encoding.ASCII.GetBytes("leaf")); + var n = l.Clone(); + n.Value = Encoding.ASCII.GetBytes("value"); + Assert.AreEqual("leaf", Encoding.ASCII.GetString(l.Value.Span)); + } + + [TestMethod] + public void TestNewExtensionException() + { + Assert.ThrowsException(() => Node.NewExtension(null, new Node())); + Assert.ThrowsException(() => Node.NewExtension(new byte[] { 0x01 }, null)); + Assert.ThrowsException(() => Node.NewExtension(Array.Empty(), new Node())); + } + + [TestMethod] + public void TestNewHashException() + { + Assert.ThrowsException(() => Node.NewHash(null)); + } + + [TestMethod] + public void TestNewLeafException() + { + Assert.ThrowsException(() => Node.NewLeaf(null)); + } + + [TestMethod] + public void TestSize() + { + var n = new Node(); + Assert.AreEqual(1, n.Size); + n = Node.NewBranch(); + Assert.AreEqual(19, n.Size); + n = Node.NewExtension(new byte[] { 0x00 }, new Node()); + Assert.AreEqual(5, n.Size); + n = Node.NewLeaf(new byte[] { 0x00 }); + Assert.AreEqual(4, n.Size); + n = Node.NewHash(UInt256.Zero); + Assert.AreEqual(33, n.Size); + } + + [TestMethod] + public void TestFromReplica() + { + var l = Node.NewLeaf(new byte[] { 0x00 }); + var n = Node.NewBranch(); + n.Children[1] = l; + var r = new Node(); + r.FromReplica(n); + Assert.AreEqual(n.Hash, r.Hash); + Assert.AreEqual(NodeType.HashNode, r.Children[1].Type); + Assert.AreEqual(l.Hash, r.Children[1].Hash); + } + + [TestMethod] + public void TestEmptyLeaf() + { + var leaf = Node.NewLeaf(Array.Empty()); + var data = leaf.ToArray(); + Assert.AreEqual(3, data.Length); + var l = data.AsSerializable(); + Assert.AreEqual(NodeType.LeafNode, l.Type); + Assert.AreEqual(0, l.Value.Length); + } + } +} diff --git a/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Trie.cs b/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Trie.cs new file mode 100644 index 0000000000..372de6a738 --- /dev/null +++ b/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Trie.cs @@ -0,0 +1,573 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Trie.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO; +using Neo.Persistence; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using static Neo.Helper; + +namespace Neo.Cryptography.MPTTrie.Tests +{ + class TestSnapshot : ISnapshot + { + public Dictionary store = new Dictionary(ByteArrayEqualityComparer.Default); + + private byte[] StoreKey(byte[] key) + { + return Concat(key); + } + + public void Put(byte[] key, byte[] value) + { + store[key] = value; + } + + public void Delete(byte[] key) + { + store.Remove(StoreKey(key)); + } + + public void Commit() { throw new NotImplementedException(); } + + public bool Contains(byte[] key) { throw new System.NotImplementedException(); } + + public IEnumerable<(byte[] Key, byte[] Value)> Seek(byte[] key, SeekDirection direction) { throw new System.NotImplementedException(); } + + public byte[] TryGet(byte[] key) + { + var result = store.TryGetValue(StoreKey(key), out byte[] value); + if (result) return value; + return null; + } + + public void Dispose() { throw new System.NotImplementedException(); } + + public int Size => store.Count; + } + + [TestClass] + public class UT_Trie + { + private Node root; + private IStore mptdb; + + private void PutToStore(IStore store, Node node) + { + store.Put(Concat(new byte[] { 0xf0 }, node.Hash.ToArray()), node.ToArray()); + } + + [TestInitialize] + public void TestInit() + { + var b = Node.NewBranch(); + var r = Node.NewExtension("0a0c".HexToBytes(), b); + var v1 = Node.NewLeaf("abcd".HexToBytes());//key=ac01 + var v2 = Node.NewLeaf("2222".HexToBytes());//key=ac + var v3 = Node.NewLeaf(Encoding.ASCII.GetBytes("existing"));//key=acae + var v4 = Node.NewLeaf(Encoding.ASCII.GetBytes("missing")); + var h3 = Node.NewHash(v3.Hash); + var e1 = Node.NewExtension(new byte[] { 0x01 }, v1); + var e3 = Node.NewExtension(new byte[] { 0x0e }, h3); + var e4 = Node.NewExtension(new byte[] { 0x01 }, v4); + b.Children[0] = e1; + b.Children[10] = e3; + b.Children[16] = v2; + b.Children[15] = Node.NewHash(e4.Hash); + this.root = r; + this.mptdb = new MemoryStore(); + PutToStore(mptdb, r); + PutToStore(mptdb, b); + PutToStore(mptdb, e1); + PutToStore(mptdb, e3); + PutToStore(mptdb, v1); + PutToStore(mptdb, v2); + PutToStore(mptdb, v3); + } + + [TestMethod] + public void TestTryGet() + { + var mpt = new Trie(mptdb.GetSnapshot(), root.Hash); + Assert.ThrowsException(() => mpt[Array.Empty()]); + Assert.AreEqual("abcd", mpt["ac01".HexToBytes()].ToHexString()); + Assert.AreEqual("2222", mpt["ac".HexToBytes()].ToHexString()); + Assert.ThrowsException(() => mpt["ab99".HexToBytes()]); + Assert.ThrowsException(() => mpt["ac39".HexToBytes()]); + Assert.ThrowsException(() => mpt["ac02".HexToBytes()]); + Assert.ThrowsException(() => mpt["ac0100".HexToBytes()]); + Assert.ThrowsException(() => mpt["ac9910".HexToBytes()]); + Assert.ThrowsException(() => mpt["acf1".HexToBytes()]); + } + + [TestMethod] + public void TestTryGetResolve() + { + var mpt = new Trie(mptdb.GetSnapshot(), root.Hash); + Assert.AreEqual(Encoding.ASCII.GetBytes("existing").ToHexString(), mpt["acae".HexToBytes()].ToHexString()); + } + + [TestMethod] + public void TestTryPut() + { + var store = new MemoryStore(); + var mpt = new Trie(store.GetSnapshot(), null); + mpt.Put("ac01".HexToBytes(), "abcd".HexToBytes()); + mpt.Put("ac".HexToBytes(), "2222".HexToBytes()); + mpt.Put("acae".HexToBytes(), Encoding.ASCII.GetBytes("existing")); + mpt.Put("acf1".HexToBytes(), Encoding.ASCII.GetBytes("missing")); + Assert.AreEqual(root.Hash.ToString(), mpt.Root.Hash.ToString()); + Assert.ThrowsException(() => mpt.Put(Array.Empty(), "01".HexToBytes())); + mpt.Put("01".HexToBytes(), Array.Empty()); + Assert.ThrowsException(() => mpt.Put(new byte[Node.MaxKeyLength / 2 + 1], Array.Empty())); + Assert.ThrowsException(() => mpt.Put("01".HexToBytes(), new byte[Node.MaxValueLength + 1])); + mpt.Put("ac01".HexToBytes(), "ab".HexToBytes()); + } + + [TestMethod] + public void TestPutCantResolve() + { + var mpt = new Trie(mptdb.GetSnapshot(), root.Hash); + Assert.ThrowsException(() => mpt.Put("acf111".HexToBytes(), new byte[] { 1 })); + } + + [TestMethod] + public void TestTryDelete() + { + var mpt = new Trie(mptdb.GetSnapshot(), root.Hash); + Assert.IsNotNull(mpt["ac".HexToBytes()]); + Assert.IsFalse(mpt.Delete("0c99".HexToBytes())); + Assert.ThrowsException(() => mpt.Delete(Array.Empty())); + Assert.IsFalse(mpt.Delete("ac20".HexToBytes())); + Assert.ThrowsException(() => mpt.Delete("acf1".HexToBytes())); + Assert.IsTrue(mpt.Delete("ac".HexToBytes())); + Assert.IsFalse(mpt.Delete("acae01".HexToBytes())); + Assert.IsTrue(mpt.Delete("acae".HexToBytes())); + Assert.AreEqual("0xcb06925428b7c727375c7fdd943a302fe2c818cf2e2eaf63a7932e3fd6cb3408", mpt.Root.Hash.ToString()); + } + + [TestMethod] + public void TestDeleteRemainCanResolve() + { + var store = new MemoryStore(); + var snapshot = store.GetSnapshot(); + var mpt1 = new Trie(snapshot, null); + mpt1.Put("ac00".HexToBytes(), "abcd".HexToBytes()); + mpt1.Put("ac10".HexToBytes(), "abcd".HexToBytes()); + mpt1.Commit(); + snapshot.Commit(); + var snapshot2 = store.GetSnapshot(); + var mpt2 = new Trie(snapshot2, mpt1.Root.Hash); + Assert.IsTrue(mpt2.Delete("ac00".HexToBytes())); + mpt2.Commit(); + snapshot2.Commit(); + Assert.IsTrue(mpt2.Delete("ac10".HexToBytes())); + } + + [TestMethod] + public void TestDeleteRemainCantResolve() + { + var b = Node.NewBranch(); + var r = Node.NewExtension("0a0c".HexToBytes(), b); + var v1 = Node.NewLeaf("abcd".HexToBytes());//key=ac01 + var v4 = Node.NewLeaf(Encoding.ASCII.GetBytes("missing")); + var e1 = Node.NewExtension(new byte[] { 0x01 }, v1); + var e4 = Node.NewExtension(new byte[] { 0x01 }, v4); + b.Children[0] = e1; + b.Children[15] = Node.NewHash(e4.Hash); + var store = new MemoryStore(); + PutToStore(store, r); + PutToStore(store, b); + PutToStore(store, e1); + PutToStore(store, v1); + + var snapshot = store.GetSnapshot(); + var mpt = new Trie(snapshot, r.Hash); + Assert.ThrowsException(() => mpt.Delete("ac01".HexToBytes())); + } + + + [TestMethod] + public void TestDeleteSameValue() + { + var store = new MemoryStore(); + var snapshot = store.GetSnapshot(); + var mpt = new Trie(snapshot, null); + mpt.Put("ac01".HexToBytes(), "abcd".HexToBytes()); + mpt.Put("ac02".HexToBytes(), "abcd".HexToBytes()); + Assert.IsNotNull(mpt["ac01".HexToBytes()]); + Assert.IsNotNull(mpt["ac02".HexToBytes()]); + mpt.Delete("ac01".HexToBytes()); + Assert.IsNotNull(mpt["ac02".HexToBytes()]); + mpt.Commit(); + snapshot.Commit(); + var mpt0 = new Trie(store.GetSnapshot(), mpt.Root.Hash); + Assert.IsNotNull(mpt0["ac02".HexToBytes()]); + } + + [TestMethod] + public void TestBranchNodeRemainValue() + { + var snapshot = new TestSnapshot(); + var mpt = new Trie(snapshot, null); + mpt.Put("ac11".HexToBytes(), "ac11".HexToBytes()); + mpt.Put("ac22".HexToBytes(), "ac22".HexToBytes()); + mpt.Put("ac".HexToBytes(), "ac".HexToBytes()); + mpt.Commit(); + Assert.AreEqual(7, snapshot.Size); + Assert.IsTrue(mpt.Delete("ac11".HexToBytes())); + mpt.Commit(); + Assert.AreEqual(5, snapshot.Size); + Assert.IsTrue(mpt.Delete("ac22".HexToBytes())); + Assert.IsNotNull(mpt["ac".HexToBytes()]); + mpt.Commit(); + Assert.AreEqual(2, snapshot.Size); + } + + [TestMethod] + public void TestGetProof() + { + var b = Node.NewBranch(); + var r = Node.NewExtension("0a0c".HexToBytes(), b); + var v1 = Node.NewLeaf("abcd".HexToBytes());//key=ac01 + var v2 = Node.NewLeaf("2222".HexToBytes());//key=ac + var v3 = Node.NewLeaf(Encoding.ASCII.GetBytes("existing"));//key=acae + var v4 = Node.NewLeaf(Encoding.ASCII.GetBytes("missing")); + var h3 = Node.NewHash(v3.Hash); + var e1 = Node.NewExtension(new byte[] { 0x01 }, v1); + var e3 = Node.NewExtension(new byte[] { 0x0e }, h3); + var e4 = Node.NewExtension(new byte[] { 0x01 }, v4); + b.Children[0] = e1; + b.Children[10] = e3; + b.Children[16] = v2; + b.Children[15] = Node.NewHash(e4.Hash); + + var mpt = new Trie(mptdb.GetSnapshot(), r.Hash); + Assert.AreEqual(r.Hash.ToString(), mpt.Root.Hash.ToString()); + var result = mpt.TryGetProof("ac01".HexToBytes(), out var proof); + Assert.IsTrue(result); + Assert.AreEqual(4, proof.Count); + Assert.IsTrue(proof.Contains(b.ToArrayWithoutReference())); + Assert.IsTrue(proof.Contains(r.ToArrayWithoutReference())); + Assert.IsTrue(proof.Contains(e1.ToArrayWithoutReference())); + Assert.IsTrue(proof.Contains(v1.ToArrayWithoutReference())); + + result = mpt.TryGetProof("ac".HexToBytes(), out proof); + Assert.AreEqual(3, proof.Count); + + result = mpt.TryGetProof("ac10".HexToBytes(), out proof); + Assert.IsFalse(result); + + result = mpt.TryGetProof("acae".HexToBytes(), out proof); + Assert.AreEqual(4, proof.Count); + + Assert.ThrowsException(() => mpt.TryGetProof(Array.Empty(), out proof)); + + result = mpt.TryGetProof("ac0100".HexToBytes(), out proof); + Assert.IsFalse(result); + + Assert.ThrowsException(() => mpt.TryGetProof("acf1".HexToBytes(), out var proof)); + } + + [TestMethod] + public void TestVerifyProof() + { + var mpt = new Trie(mptdb.GetSnapshot(), root.Hash); + var result = mpt.TryGetProof("ac01".HexToBytes(), out var proof); + Assert.IsTrue(result); + var value = Trie.VerifyProof(root.Hash, "ac01".HexToBytes(), proof); + Assert.IsNotNull(value); + Assert.AreEqual(value.ToHexString(), "abcd"); + } + + [TestMethod] + public void TestAddLongerKey() + { + var store = new MemoryStore(); + var snapshot = store.GetSnapshot(); + var mpt = new Trie(snapshot, null); + mpt.Put(new byte[] { 0xab }, new byte[] { 0x01 }); + mpt.Put(new byte[] { 0xab, 0xcd }, new byte[] { 0x02 }); + Assert.AreEqual("01", mpt[new byte[] { 0xab }].ToHexString()); + } + + [TestMethod] + public void TestSplitKey() + { + var store = new MemoryStore(); + var snapshot = store.GetSnapshot(); + var mpt1 = new Trie(snapshot, null); + mpt1.Put(new byte[] { 0xab, 0xcd }, new byte[] { 0x01 }); + mpt1.Put(new byte[] { 0xab }, new byte[] { 0x02 }); + var r = mpt1.TryGetProof(new byte[] { 0xab, 0xcd }, out var set1); + Assert.IsTrue(r); + Assert.AreEqual(4, set1.Count); + var mpt2 = new Trie(snapshot, null); + mpt2.Put(new byte[] { 0xab }, new byte[] { 0x02 }); + mpt2.Put(new byte[] { 0xab, 0xcd }, new byte[] { 0x01 }); + r = mpt2.TryGetProof(new byte[] { 0xab, 0xcd }, out var set2); + Assert.IsTrue(r); + Assert.AreEqual(4, set2.Count); + Assert.AreEqual(mpt1.Root.Hash, mpt2.Root.Hash); + } + + [TestMethod] + public void TestFind() + { + var store = new MemoryStore(); + var snapshot = store.GetSnapshot(); + var mpt1 = new Trie(snapshot, null); + var results = mpt1.Find(ReadOnlySpan.Empty).ToArray(); + Assert.AreEqual(0, results.Length); + var mpt2 = new Trie(snapshot, null); + mpt2.Put(new byte[] { 0xab, 0xcd, 0xef }, new byte[] { 0x01 }); + mpt2.Put(new byte[] { 0xab, 0xcd, 0xe1 }, new byte[] { 0x02 }); + mpt2.Put(new byte[] { 0xab }, new byte[] { 0x03 }); + results = mpt2.Find(ReadOnlySpan.Empty).ToArray(); + Assert.AreEqual(3, results.Length); + results = mpt2.Find(new byte[] { 0xab }).ToArray(); + Assert.AreEqual(3, results.Length); + results = mpt2.Find(new byte[] { 0xab, 0xcd }).ToArray(); + Assert.AreEqual(2, results.Length); + results = mpt2.Find(new byte[] { 0xac }).ToArray(); + Assert.AreEqual(0, results.Length); + results = mpt2.Find(new byte[] { 0xab, 0xcd, 0xef, 0x00 }).ToArray(); + Assert.AreEqual(0, results.Length); + } + + [TestMethod] + public void TestFindCantResolve() + { + var b = Node.NewBranch(); + var r = Node.NewExtension("0a0c".HexToBytes(), b); + var v1 = Node.NewLeaf("abcd".HexToBytes());//key=ac01 + var v4 = Node.NewLeaf(Encoding.ASCII.GetBytes("missing")); + var e1 = Node.NewExtension(new byte[] { 0x01 }, v1); + var e4 = Node.NewExtension(new byte[] { 0x01 }, v4); + b.Children[0] = e1; + b.Children[15] = Node.NewHash(e4.Hash); + var store = new MemoryStore(); + PutToStore(store, r); + PutToStore(store, b); + PutToStore(store, e1); + PutToStore(store, v1); + + var snapshot = store.GetSnapshot(); + var mpt = new Trie(snapshot, r.Hash); + Assert.ThrowsException(() => mpt.Find("ac".HexToBytes()).Count()); + } + + [TestMethod] + public void TestFindLeadNode() + { + // r.Key = 0x0a0c + // b.Key = 0x00 + // l1.Key = 0x01 + var mpt = new Trie(mptdb.GetSnapshot(), root.Hash); + var prefix = new byte[] { 0xac, 0x01 }; // = FromNibbles(path = { 0x0a, 0x0c, 0x00, 0x01 }); + var results = mpt.Find(prefix).ToArray(); + Assert.AreEqual(1, results.Count()); + + prefix = new byte[] { 0xac }; // = FromNibbles(path = { 0x0a, 0x0c }); + Assert.ThrowsException(() => mpt.Find(prefix).ToArray()); + } + + [TestMethod] + public void TestFromNibblesException() + { + var b = Node.NewBranch(); + var r = Node.NewExtension("0c".HexToBytes(), b); + var v1 = Node.NewLeaf("abcd".HexToBytes());//key=ac01 + var v2 = Node.NewLeaf("2222".HexToBytes());//key=ac + var e1 = Node.NewExtension(new byte[] { 0x01 }, v1); + b.Children[0] = e1; + b.Children[16] = v2; + var store = new MemoryStore(); + PutToStore(store, r); + PutToStore(store, b); + PutToStore(store, e1); + PutToStore(store, v1); + PutToStore(store, v2); + + var snapshot = store.GetSnapshot(); + var mpt = new Trie(snapshot, r.Hash); + Assert.ThrowsException(() => mpt.Find(Array.Empty()).Count()); + } + + [TestMethod] + public void TestReference1() + { + var store = new MemoryStore(); + var snapshot = store.GetSnapshot(); + var mpt = new Trie(snapshot, null); + mpt.Put("a101".HexToBytes(), "01".HexToBytes()); + mpt.Put("a201".HexToBytes(), "01".HexToBytes()); + mpt.Put("a301".HexToBytes(), "01".HexToBytes()); + mpt.Commit(); + snapshot.Commit(); + var snapshot1 = store.GetSnapshot(); + var mpt1 = new Trie(snapshot1, mpt.Root.Hash); + mpt1.Delete("a301".HexToBytes()); + mpt1.Commit(); + snapshot1.Commit(); + var snapshot2 = store.GetSnapshot(); + var mpt2 = new Trie(snapshot2, mpt1.Root.Hash); + mpt2.Delete("a201".HexToBytes()); + Assert.AreEqual("01", mpt2["a101".HexToBytes()].ToHexString()); + } + + [TestMethod] + public void TestReference2() + { + var snapshot = new TestSnapshot(); + var mpt = new Trie(snapshot, null); + mpt.Put("a101".HexToBytes(), "01".HexToBytes()); + mpt.Put("a201".HexToBytes(), "01".HexToBytes()); + mpt.Put("a301".HexToBytes(), "01".HexToBytes()); + mpt.Commit(); + Assert.AreEqual(4, snapshot.Size); + mpt.Delete("a301".HexToBytes()); + mpt.Commit(); + Assert.AreEqual(4, snapshot.Size); + mpt.Delete("a201".HexToBytes()); + mpt.Commit(); + Assert.AreEqual(2, snapshot.Size); + Assert.AreEqual("01", mpt["a101".HexToBytes()].ToHexString()); + } + + + [TestMethod] + public void TestExtensionDeleteDirty() + { + var snapshot = new TestSnapshot(); + var mpt = new Trie(snapshot, null); + mpt.Put("a1".HexToBytes(), "01".HexToBytes()); + mpt.Put("a2".HexToBytes(), "02".HexToBytes()); + mpt.Commit(); + Assert.AreEqual(4, snapshot.Size); + var mpt1 = new Trie(snapshot, mpt.Root.Hash); + mpt1.Delete("a1".HexToBytes()); + mpt1.Commit(); + Assert.AreEqual(2, snapshot.Size); + var mpt2 = new Trie(snapshot, mpt1.Root.Hash); + mpt2.Delete("a2".HexToBytes()); + mpt2.Commit(); + Assert.AreEqual(0, snapshot.Size); + } + + [TestMethod] + public void TestBranchDeleteDirty() + { + var snapshot = new TestSnapshot(); + var mpt = new Trie(snapshot, null); + mpt.Put("10".HexToBytes(), "01".HexToBytes()); + mpt.Put("20".HexToBytes(), "02".HexToBytes()); + mpt.Put("30".HexToBytes(), "03".HexToBytes()); + mpt.Commit(); + Assert.AreEqual(7, snapshot.Size); + var mpt1 = new Trie(snapshot, mpt.Root.Hash); + mpt1.Delete("10".HexToBytes()); + mpt1.Commit(); + Assert.AreEqual(5, snapshot.Size); + var mpt2 = new Trie(snapshot, mpt1.Root.Hash); + mpt2.Delete("20".HexToBytes()); + mpt2.Commit(); + Assert.AreEqual(2, snapshot.Size); + var mpt3 = new Trie(snapshot, mpt2.Root.Hash); + mpt3.Delete("30".HexToBytes()); + mpt3.Commit(); + Assert.AreEqual(0, snapshot.Size); + } + + [TestMethod] + public void TestExtensionPutDirty() + { + var snapshot = new TestSnapshot(); + var mpt = new Trie(snapshot, null); + mpt.Put("a1".HexToBytes(), "01".HexToBytes()); + mpt.Put("a2".HexToBytes(), "02".HexToBytes()); + mpt.Commit(); + Assert.AreEqual(4, snapshot.Size); + var mpt1 = new Trie(snapshot, mpt.Root.Hash); + mpt1.Put("a3".HexToBytes(), "03".HexToBytes()); + mpt1.Commit(); + Assert.AreEqual(5, snapshot.Size); + } + + [TestMethod] + public void TestBranchPutDirty() + { + var snapshot = new TestSnapshot(); + var mpt = new Trie(snapshot, null); + mpt.Put("10".HexToBytes(), "01".HexToBytes()); + mpt.Put("20".HexToBytes(), "02".HexToBytes()); + mpt.Commit(); + Assert.AreEqual(5, snapshot.Size); + var mpt1 = new Trie(snapshot, mpt.Root.Hash); + mpt1.Put("30".HexToBytes(), "03".HexToBytes()); + mpt1.Commit(); + Assert.AreEqual(7, snapshot.Size); + } + + [TestMethod] + public void TestEmptyValueIssue633() + { + var key = "01".HexToBytes(); + var snapshot = new TestSnapshot(); + var mpt = new Trie(snapshot, null); + mpt.Put(key, Array.Empty()); + var val = mpt[key]; + Assert.IsNotNull(val); + Assert.AreEqual(0, val.Length); + var r = mpt.TryGetProof(key, out var proof); + Assert.IsTrue(r); + val = Trie.VerifyProof(mpt.Root.Hash, key, proof); + Assert.IsNotNull(val); + Assert.AreEqual(0, val.Length); + } + + [TestMethod] + public void TestFindWithFrom() + { + var snapshot = new TestSnapshot(); + var mpt = new Trie(snapshot, null); + mpt.Put("aa".HexToBytes(), "02".HexToBytes()); + mpt.Put("aa10".HexToBytes(), "03".HexToBytes()); + mpt.Put("aa50".HexToBytes(), "04".HexToBytes()); + var r = mpt.Find("aa".HexToBytes()).ToList(); + Assert.AreEqual(3, r.Count); + r = mpt.Find("aa".HexToBytes(), "aa30".HexToBytes()).ToList(); + Assert.AreEqual(1, r.Count); + r = mpt.Find("aa".HexToBytes(), "aa60".HexToBytes()).ToList(); + Assert.AreEqual(0, r.Count); + r = mpt.Find("aa".HexToBytes(), "aa10".HexToBytes()).ToList(); + Assert.AreEqual(1, r.Count); + } + + [TestMethod] + public void TestFindStatesIssue652() + { + var snapshot = new TestSnapshot(); + var mpt = new Trie(snapshot, null); + mpt.Put("abc1".HexToBytes(), "01".HexToBytes()); + mpt.Put("abc3".HexToBytes(), "02".HexToBytes()); + var r = mpt.Find("ab".HexToBytes(), "abd2".HexToBytes()).ToList(); + Assert.AreEqual(0, r.Count); + r = mpt.Find("ab".HexToBytes(), "abb2".HexToBytes()).ToList(); + Assert.AreEqual(2, r.Count); + r = mpt.Find("ab".HexToBytes(), "abc2".HexToBytes()).ToList(); + Assert.AreEqual(1, r.Count); + } + } +} diff --git a/tests/Neo.Cryptography.MPTTrie.Tests/Neo.Cryptography.MPTTrie.Tests.csproj b/tests/Neo.Cryptography.MPTTrie.Tests/Neo.Cryptography.MPTTrie.Tests.csproj new file mode 100644 index 0000000000..3691fba90a --- /dev/null +++ b/tests/Neo.Cryptography.MPTTrie.Tests/Neo.Cryptography.MPTTrie.Tests.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + Neo.Cryptography.MPT.Tests + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Neo.Json.UnitTests/Neo.Json.UnitTests.csproj b/tests/Neo.Json.UnitTests/Neo.Json.UnitTests.csproj index 3643b6ec17..81e70ebbe1 100644 --- a/tests/Neo.Json.UnitTests/Neo.Json.UnitTests.csproj +++ b/tests/Neo.Json.UnitTests/Neo.Json.UnitTests.csproj @@ -1,11 +1,18 @@ - - - - enable - - - - - - - + + + + net8.0 + enable + + + + + + + + + + + + + diff --git a/tests/Neo.Json.UnitTests/UT_JArray.cs b/tests/Neo.Json.UnitTests/UT_JArray.cs index e838be9c92..40388d11fc 100644 --- a/tests/Neo.Json.UnitTests/UT_JArray.cs +++ b/tests/Neo.Json.UnitTests/UT_JArray.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_JArray.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using System.Collections; namespace Neo.Json.UnitTests diff --git a/tests/Neo.Json.UnitTests/UT_JBoolean.cs b/tests/Neo.Json.UnitTests/UT_JBoolean.cs index 679c9b622d..3ab19bd1b3 100644 --- a/tests/Neo.Json.UnitTests/UT_JBoolean.cs +++ b/tests/Neo.Json.UnitTests/UT_JBoolean.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_JBoolean.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + namespace Neo.Json.UnitTests { [TestClass] @@ -19,5 +30,14 @@ public void TestAsNumber() jFalse.AsNumber().Should().Be(0); jTrue.AsNumber().Should().Be(1); } + + [TestMethod] + public void TestEqual() + { + Assert.IsTrue(jTrue.Equals(new JBoolean(true))); + Assert.IsTrue(jTrue == new JBoolean(true)); + Assert.IsTrue(jFalse.Equals(new JBoolean())); + Assert.IsTrue(jFalse == new JBoolean()); + } } } diff --git a/tests/Neo.Json.UnitTests/UT_JNumber.cs b/tests/Neo.Json.UnitTests/UT_JNumber.cs index 6156bc1cb5..6eb0598fd3 100644 --- a/tests/Neo.Json.UnitTests/UT_JNumber.cs +++ b/tests/Neo.Json.UnitTests/UT_JNumber.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_JNumber.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + namespace Neo.Json.UnitTests { enum Woo @@ -49,9 +60,18 @@ public void TestGetEnum() new JNumber(1).GetEnum().Should().Be(Woo.Jerry); new JNumber(2).GetEnum().Should().Be(Woo.James); new JNumber(3).AsEnum().Should().Be(Woo.Tom); - Action action = () => new JNumber(3).GetEnum(); action.Should().Throw(); } + + [TestMethod] + public void TestEqual() + { + Assert.IsTrue(maxInt.Equals(JNumber.MAX_SAFE_INTEGER)); + Assert.IsTrue(maxInt == JNumber.MAX_SAFE_INTEGER); + Assert.IsTrue(minInt.Equals(JNumber.MIN_SAFE_INTEGER)); + Assert.IsTrue(minInt == JNumber.MIN_SAFE_INTEGER); + Assert.IsTrue(zero == new JNumber()); + } } } diff --git a/tests/Neo.Json.UnitTests/UT_JObject.cs b/tests/Neo.Json.UnitTests/UT_JObject.cs index 2ff3d25c02..e3660a7d3e 100644 --- a/tests/Neo.Json.UnitTests/UT_JObject.cs +++ b/tests/Neo.Json.UnitTests/UT_JObject.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_JObject.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + namespace Neo.Json.UnitTests { [TestClass] @@ -106,7 +117,7 @@ public void TestGetNull() [TestMethod] public void TestClone() { - var bobClone = bob.Clone(); + var bobClone = (JObject)bob.Clone(); bobClone.Should().NotBeSameAs(bob); foreach (var key in bobClone.Properties.Keys) { diff --git a/tests/Neo.Json.UnitTests/UT_JPath.cs b/tests/Neo.Json.UnitTests/UT_JPath.cs index be2733c4ff..9d958b1cde 100644 --- a/tests/Neo.Json.UnitTests/UT_JPath.cs +++ b/tests/Neo.Json.UnitTests/UT_JPath.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_JPath.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + namespace Neo.Json.UnitTests { [TestClass] diff --git a/tests/Neo.Json.UnitTests/UT_JString.cs b/tests/Neo.Json.UnitTests/UT_JString.cs index 3188bf0e59..7e2bec9834 100644 --- a/tests/Neo.Json.UnitTests/UT_JString.cs +++ b/tests/Neo.Json.UnitTests/UT_JString.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_JString.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + namespace Neo.Json.UnitTests { [TestClass] @@ -48,5 +59,14 @@ public void TestGetEnum() woo = s.AsEnum(Woo.Jerry, false); Assert.AreEqual(Woo.Jerry, woo); } + [TestMethod] + public void TestEqual() + { + var str = "hello world"; + var jString = new JString(str); + Assert.IsTrue(jString.Equals(str)); + Assert.IsTrue(jString == str); + Assert.IsTrue(jString != "hello world2"); + } } } diff --git a/tests/Neo.Json.UnitTests/UT_OrderedDictionary.cs b/tests/Neo.Json.UnitTests/UT_OrderedDictionary.cs index e8b112b711..0f73afae95 100644 --- a/tests/Neo.Json.UnitTests/UT_OrderedDictionary.cs +++ b/tests/Neo.Json.UnitTests/UT_OrderedDictionary.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_OrderedDictionary.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using System.Collections; namespace Neo.Json.UnitTests @@ -10,10 +21,12 @@ public class UT_OrderedDictionary [TestInitialize] public void SetUp() { - od = new OrderedDictionary(); - od.Add("a", 1); - od.Add("b", 2); - od.Add("c", 3); + od = new OrderedDictionary + { + { "a", 1 }, + { "b", 2 }, + { "c", 3 } + }; } [TestMethod] diff --git a/tests/Neo.Json.UnitTests/Usings.cs b/tests/Neo.Json.UnitTests/Usings.cs index 6f971bbc56..e79662735c 100644 --- a/tests/Neo.Json.UnitTests/Usings.cs +++ b/tests/Neo.Json.UnitTests/Usings.cs @@ -1,2 +1,13 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Usings.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + global using FluentAssertions; global using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/tests/Neo.Network.RPC.Tests/Neo.Network.RPC.Tests.csproj b/tests/Neo.Network.RPC.Tests/Neo.Network.RPC.Tests.csproj new file mode 100644 index 0000000000..13a13f5c98 --- /dev/null +++ b/tests/Neo.Network.RPC.Tests/Neo.Network.RPC.Tests.csproj @@ -0,0 +1,26 @@ + + + + net8.0 + Neo.Network.RPC.Tests + + + + + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/tests/Neo.Network.RPC.Tests/RpcTestCases.json b/tests/Neo.Network.RPC.Tests/RpcTestCases.json new file mode 100644 index 0000000000..cfbffb3ede --- /dev/null +++ b/tests/Neo.Network.RPC.Tests/RpcTestCases.json @@ -0,0 +1,3340 @@ +[ + { + "Name": "sendrawtransactionasyncerror", + "Request": { + "jsonrpc": "2.0", + "method": "sendrawtransaction", + "params": [ "ANIHn05ujtUAAAAAACYcEwAAAAAAQkEAAAEKo4e1Ppa3mJpjFDGgVt0fQKBC9gEAXQMAyBeoBAAAAAwUzViuz9M1vh6z0xHh3IAJY9/XLZ8MFAqjh7U+lreYmmMUMaBW3R9AoEL2E8AMCHRyYW5zZmVyDBSlB7dGdv/td+dUuG7NmQnwus08ukFifVtSOAFCDEDh8zgTrGUXyzVX60wBCMyajNRfzFRiEPAe8CgGQ10bA2C3fnVz68Gw+Amgn5gmvuNfYKgWQ/W68Km1bYUPlnEYKQwhA86j4vgfGvk1ItKe3k8kofC+3q1ykzkdM4gPVHXZeHjJC0GVRA14" ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "error": { + "code": -500, + "message": "InsufficientFunds", + "data": " at Neo.Plugins.RpcServer.GetRelayResult(RelayResultReason reason, UInt256 hash)\r\n at Neo.Network.RPC.Models.RpcServer.SendRawTransaction(JArray _params)\r\n at Neo.Network.RPC.Models.RpcServer.ProcessRequest(HttpContext context, JObject request)" + } + } + }, + { + "Name": "getbestblockhashasync", + "Request": { + "jsonrpc": "2.0", + "method": "getbestblockhash", + "params": [], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": "0x530de76326a8662d1b730ba4fbdf011051eabd142015587e846da42376adf35f" + } + }, + { + "Name": "getblockhexasync", + "Request": { + "jsonrpc": "2.0", + "method": "getblock", + "params": [ 0 ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": "0000000000000000000000000000000000000000000000000000000000000000000000002bbb6298fc7039330cdfd2e4dfbe976ee72c4cba6c16d68f0b49ab1bca685b7388ea19ef55010000000000009903b0c3d292988febe5f306a02f654ea2eb16290100011102001dac2b7c000000000000000000ca61e52e881d41374e640f819cd118cc153b21a7000000000000000000000000000000000000000000000541123e7fe801000111" + } + }, + { + "Name": "getblockhexasync", + "Request": { + "jsonrpc": "2.0", + "method": "getblock", + "params": [ "0xe191fe1aea732c3e23f20af8a95e09f95891176f8064a2fce8571d51f80619a8" ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": "0000000000000000000000000000000000000000000000000000000000000000000000002bbb6298fc7039330cdfd2e4dfbe976ee72c4cba6c16d68f0b49ab1bca685b7388ea19ef55010000000000009903b0c3d292988febe5f306a02f654ea2eb16290100011102001dac2b7c000000000000000000ca61e52e881d41374e640f819cd118cc153b21a7000000000000000000000000000000000000000000000541123e7fe801000111" + } + }, + { + "Name": "getblockasync", + "Request": { + "jsonrpc": "2.0", + "method": "getblock", + "params": [ 7, true ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "hash": "0x6d1556889c92249da88d2fb7729ae82fb2cc1b45dcd9030a40208b72a1d3cb83", + "size": 470, + "version": 0, + "previousblockhash": "0xaae8867e9086afaf06fd02cc538e88a69b801abd6f9d3ae39ae630e29d5b39e2", + "merkleroot": "0xe95761f21c733ad53066786af24ee5d613b32bd5aae538df2d611492ec0cae82", + "time": 1594867377561, + "nonce": "FFFFFFFFFFFFFFFF", + "index": 7, + "primary": 1, + "nextconsensus": "NikvsLcNP1jWhrFPrfS3n4spEASgdNYTG2", + "witnesses": [ + { + "invocation": "DEBs6hZDHUtL7KOJuF1m8/vITM8VeduwegKhBdbqcLKdBzXA1uZZiBl8jM/rhjXBaIGQSFIQuq8Er1Nb5y5/DWUx", + "verification": "EQwhAqnqaELMDLOw8jF7B8hQ3j0eKyQ6mO0tVqP/TKZqrzMLEQtBE43vrw==" + } + ], + "tx": [ + { + "hash": "0x83d44d71d59f854bc29f4e3932bf68703545807d05fb5429504d70cfc8d05071", + "size": 248, + "version": 0, + "nonce": 631973574, + "sender": "NikvsLcNP1jWhrFPrfS3n4spEASgdNYTG2", + "sysfee": "9007990", + "netfee": "1248450", + "validuntilblock": 2102405, + "signers": [ + { + "account": "0xe19de267a37a71734478f512b3e92c79fc3695fa", + "scopes": "CalledByEntry" + } + ], + "attributes": [], + "script": "AccyDBQcA1dGS3d\u002Bz2tfOsOJOs4fixYh9gwU\u002BpU2/Hks6bMS9XhEc3F6o2fineETwAwIdHJhbnNmZXIMFCUFnstIeNOodfkcUc7e0zDUV1/eQWJ9W1I4", + "witnesses": [ + { + "invocation": "DEDZxkskUb1aH1I4EX5ja02xrYX4fCubAmQzBuPpfY7pDEb1n4Dzx\u002BUB\u002BqSdC/CGskGf5BuzJ0MWJJipsHuivKmU", + "verification": "EQwhAqnqaELMDLOw8jF7B8hQ3j0eKyQ6mO0tVqP/TKZqrzMLEQtBE43vrw==" + } + ] + } + ], + "confirmations": 695, + "nextblockhash": "0xc4b986813396932a47d6823a9987ccee0148c6fca0150102f4b24ce05cfc9c6f" + } + } + }, + { + "Name": "getblockasync", + "Request": { + "jsonrpc": "2.0", + "method": "getblock", + "params": [ "0xb9579b028e4cf31a0c3bd9582f9f7fbd40b0e0495604406b8f530c7ebce5bcc8", true ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "hash": "0x6d1556889c92249da88d2fb7729ae82fb2cc1b45dcd9030a40208b72a1d3cb83", + "size": 470, + "version": 0, + "previousblockhash": "0xaae8867e9086afaf06fd02cc538e88a69b801abd6f9d3ae39ae630e29d5b39e2", + "merkleroot": "0xe95761f21c733ad53066786af24ee5d613b32bd5aae538df2d611492ec0cae82", + "time": 1594867377561, + "nonce": "FFFFFFFFFFFFFFFF", + "index": 7, + "primary": 1, + "nextconsensus": "NikvsLcNP1jWhrFPrfS3n4spEASgdNYTG2", + "witnesses": [ + { + "invocation": "DEBs6hZDHUtL7KOJuF1m8/vITM8VeduwegKhBdbqcLKdBzXA1uZZiBl8jM/rhjXBaIGQSFIQuq8Er1Nb5y5/DWUx", + "verification": "EQwhAqnqaELMDLOw8jF7B8hQ3j0eKyQ6mO0tVqP/TKZqrzMLEQtBE43vrw==" + } + ], + "tx": [ + { + "hash": "0x83d44d71d59f854bc29f4e3932bf68703545807d05fb5429504d70cfc8d05071", + "size": 248, + "version": 0, + "nonce": 631973574, + "sender": "NikvsLcNP1jWhrFPrfS3n4spEASgdNYTG2", + "sysfee": "9007990", + "netfee": "1248450", + "validuntilblock": 2102405, + "signers": [ + { + "account": "0xe19de267a37a71734478f512b3e92c79fc3695fa", + "scopes": "CalledByEntry" + } + ], + "attributes": [], + "script": "AccyDBQcA1dGS3d\u002Bz2tfOsOJOs4fixYh9gwU\u002BpU2/Hks6bMS9XhEc3F6o2fineETwAwIdHJhbnNmZXIMFCUFnstIeNOodfkcUc7e0zDUV1/eQWJ9W1I4", + "witnesses": [ + { + "invocation": "DEDZxkskUb1aH1I4EX5ja02xrYX4fCubAmQzBuPpfY7pDEb1n4Dzx\u002BUB\u002BqSdC/CGskGf5BuzJ0MWJJipsHuivKmU", + "verification": "EQwhAqnqaELMDLOw8jF7B8hQ3j0eKyQ6mO0tVqP/TKZqrzMLEQtBE43vrw==" + } + ] + } + ], + "confirmations": 695, + "nextblockhash": "0xc4b986813396932a47d6823a9987ccee0148c6fca0150102f4b24ce05cfc9c6f" + } + } + }, + { + "Name": "getblockheadercountasync", + "Request": { + "jsonrpc": "2.0", + "method": "getblockheadercount", + "params": [], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": 3825 + } + }, + { + "Name": "getblockcountasync", + "Request": { + "jsonrpc": "2.0", + "method": "getblockcount", + "params": [], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": 2691 + } + }, + { + "Name": "getblockhashasync", + "Request": { + "jsonrpc": "2.0", + "method": "getblockhash", + "params": [ 0 ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": "0xe191fe1aea732c3e23f20af8a95e09f95891176f8064a2fce8571d51f80619a8" + } + }, + { + "Name": "getblockheaderhexasync", + "Request": { + "jsonrpc": "2.0", + "method": "getblockheader", + "params": [ 0 ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": "0000000000000000000000000000000000000000000000000000000000000000000000002bbb6298fc7039330cdfd2e4dfbe976ee72c4cba6c16d68f0b49ab1bca685b7388ea19ef55010000000000009903b0c3d292988febe5f306a02f654ea2eb16290100011100" + } + }, + { + "Name": "getblockheaderhexasync", + "Request": { + "jsonrpc": "2.0", + "method": "getblockheader", + "params": [ "0xe191fe1aea732c3e23f20af8a95e09f95891176f8064a2fce8571d51f80619a8" ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": "0000000000000000000000000000000000000000000000000000000000000000000000002bbb6298fc7039330cdfd2e4dfbe976ee72c4cba6c16d68f0b49ab1bca685b7388ea19ef55010000000000009903b0c3d292988febe5f306a02f654ea2eb16290100011100" + } + }, + { + "Name": "getblockheaderasync", + "Request": { + "jsonrpc": "2.0", + "method": "getblockheader", + "params": [ 0, true ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "hash": "0xbbf7e191d4947f8a4dc33477902dacd0b047e371a81c18a6df62fe0d541725f5", + "size": 113, + "version": 0, + "previousblockhash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "merkleroot": "0x735b68ca1bab490b8fd6166cba4c2ce76e97bedfe4d2df0c333970fc9862bb2b", + "time": 1468595301000, + "nonce": "FFFFFFFFFFFFFFFF", + "index": 0, + "primary": 1, + "nextconsensus": "NZs2zXSPuuv9ZF6TDGSWT1RBmE8rfGj7UW", + "witnesses": [ + { + "invocation": "", + "verification": "EQ==" + } + ], + "confirmations": 2700, + "nextblockhash": "0x423173109798b038019b35129417b55cc4b5976ac79978dfab8ea2512d155f69" + } + } + }, + { + "Name": "getblockheaderasync", + "Request": { + "jsonrpc": "2.0", + "method": "getblockheader", + "params": [ "0x656bcb02e4fe8a19dbb15149073a5ae0bd8adc2da8504b67b112b44f68b4c9d7", true ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "hash": "0xbbf7e191d4947f8a4dc33477902dacd0b047e371a81c18a6df62fe0d541725f5", + "size": 113, + "version": 0, + "previousblockhash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "merkleroot": "0x735b68ca1bab490b8fd6166cba4c2ce76e97bedfe4d2df0c333970fc9862bb2b", + "time": 1468595301000, + "nonce": "FFFFFFFFFFFFFFFF", + "index": 0, + "primary": 1, + "nextconsensus": "NZs2zXSPuuv9ZF6TDGSWT1RBmE8rfGj7UW", + "witnesses": [ + { + "invocation": "", + "verification": "EQ==" + } + ], + "confirmations": 2700, + "nextblockhash": "0x423173109798b038019b35129417b55cc4b5976ac79978dfab8ea2512d155f69" + } + } + }, + { + "Name": "getblocksysfeeasync", + "Request": { + "jsonrpc": "2.0", + "method": "getblocksysfee", + "params": [ 100 ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": "300000000" + } + }, + { + "Name": "getcommitteeasync", + "Request": { + "jsonrpc": "2.0", + "method": "getcommittee", + "params": [], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + "02ced432397ddc44edba031c0bc3b933f28fdd9677792d7b20e6c036ddaaacf1e2" + ] + } + }, + { + "Name": "getcontractstateasync", + "Request": { + "jsonrpc": "2.0", + "method": "getcontractstate", + "params": [ "gastoken" ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "id": -4, + "updatecounter": 0, + "hash": "0xd2a4cff31913016155e38e474a2c06d08be276cf", + "nef": { + "magic": 860243278, + "compiler": "neo-core-v3.0", + "source": "", + "tokens": [], + "script": "APxBGvd7Zw==", + "checksum": 3155977747 + }, + "manifest": { + "name": "GasToken", + "groups": [], + "features": {}, + "supportedstandards": [ + "NEP-17" + ], + "abi": { + "methods": [ + { + "name": "balanceOf", + "parameters": [ + { + "name": "account", + "type": "Hash160" + } + ], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "decimals", + "parameters": [], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "symbol", + "parameters": [], + "returntype": "String", + "offset": 0, + "safe": true + }, + { + "name": "totalSupply", + "parameters": [], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "transfer", + "parameters": [ + { + "name": "from", + "type": "Hash160" + }, + { + "name": "to", + "type": "Hash160" + }, + { + "name": "amount", + "type": "Integer" + }, + { + "name": "data", + "type": "Any" + } + ], + "returntype": "Boolean", + "offset": 0, + "safe": false + } + ], + "events": [ + { + "name": "Transfer", + "parameters": [ + { + "name": "from", + "type": "Hash160" + }, + { + "name": "to", + "type": "Hash160" + }, + { + "name": "amount", + "type": "Integer" + } + ] + } + ] + }, + "permissions": [ + { + "contract": "*", + "methods": "*" + } + ], + "trusts": [], + "extra": null + } + } + } + }, + { + "Name": "getcontractstateasync", + "Request": { + "jsonrpc": "2.0", + "method": "getcontractstate", + "params": [ "0xd2a4cff31913016155e38e474a2c06d08be276cf" ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "id": -4, + "updatecounter": 0, + "hash": "0xd2a4cff31913016155e38e474a2c06d08be276cf", + "nef": { + "magic": 860243278, + "compiler": "neo-core-v3.0", + "source": "", + "tokens": [], + "script": "APxBGvd7Zw==", + "checksum": 3155977747 + }, + "manifest": { + "name": "GasToken", + "groups": [], + "features": {}, + "supportedstandards": [ + "NEP-17" + ], + "abi": { + "methods": [ + { + "name": "balanceOf", + "parameters": [ + { + "name": "account", + "type": "Hash160" + } + ], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "decimals", + "parameters": [], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "symbol", + "parameters": [], + "returntype": "String", + "offset": 0, + "safe": true + }, + { + "name": "totalSupply", + "parameters": [], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "transfer", + "parameters": [ + { + "name": "from", + "type": "Hash160" + }, + { + "name": "to", + "type": "Hash160" + }, + { + "name": "amount", + "type": "Integer" + }, + { + "name": "data", + "type": "Any" + } + ], + "returntype": "Boolean", + "offset": 0, + "safe": false + } + ], + "events": [ + { + "name": "Transfer", + "parameters": [ + { + "name": "from", + "type": "Hash160" + }, + { + "name": "to", + "type": "Hash160" + }, + { + "name": "amount", + "type": "Integer" + } + ] + } + ] + }, + "permissions": [ + { + "contract": "*", + "methods": "*" + } + ], + "trusts": [], + "extra": null + } + } + } + }, + { + "Name": "getcontractstateasync", + "Request": { + "jsonrpc": "2.0", + "id": 1, + "method": "getcontractstate", + "params": [ "neotoken" ] + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "id": -3, + "updatecounter": 0, + "hash": "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5", + "nef": { + "magic": 860243278, + "compiler": "neo-core-v3.0", + "source": "", + "tokens": [], + "script": "AP1BGvd7Zw==", + "checksum": 3921333105 + }, + "manifest": { + "name": "NeoToken", + "groups": [], + "features": {}, + "supportedstandards": [ + "NEP-17" + ], + "abi": { + "methods": [ + { + "name": "balanceOf", + "parameters": [ + { + "name": "account", + "type": "Hash160" + } + ], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "decimals", + "parameters": [], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "getCandidates", + "parameters": [], + "returntype": "Array", + "offset": 0, + "safe": true + }, + { + "name": "getCommittee", + "parameters": [], + "returntype": "Array", + "offset": 0, + "safe": true + }, + { + "name": "getGasPerBlock", + "parameters": [], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "getNextBlockValidators", + "parameters": [], + "returntype": "Array", + "offset": 0, + "safe": true + }, + { + "name": "registerCandidate", + "parameters": [ + { + "name": "pubkey", + "type": "ByteArray" + } + ], + "returntype": "Boolean", + "offset": 0, + "safe": false + }, + { + "name": "setGasPerBlock", + "parameters": [ + { + "name": "gasPerBlock", + "type": "Integer" + } + ], + "returntype": "Boolean", + "offset": 0, + "safe": false + }, + { + "name": "symbol", + "parameters": [], + "returntype": "String", + "offset": 0, + "safe": true + }, + { + "name": "totalSupply", + "parameters": [], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "transfer", + "parameters": [ + { + "name": "from", + "type": "Hash160" + }, + { + "name": "to", + "type": "Hash160" + }, + { + "name": "amount", + "type": "Integer" + }, + { + "name": "data", + "type": "Any" + } + ], + "returntype": "Boolean", + "offset": 0, + "safe": false + }, + { + "name": "unclaimedGas", + "parameters": [ + { + "name": "account", + "type": "Hash160" + }, + { + "name": "end", + "type": "Integer" + } + ], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "unregisterCandidate", + "parameters": [ + { + "name": "pubkey", + "type": "ByteArray" + } + ], + "returntype": "Boolean", + "offset": 0, + "safe": false + }, + { + "name": "vote", + "parameters": [ + { + "name": "account", + "type": "Hash160" + }, + { + "name": "voteTo", + "type": "ByteArray" + } + ], + "returntype": "Boolean", + "offset": 0, + "safe": false + } + ], + "events": [ + { + "name": "Transfer", + "parameters": [ + { + "name": "from", + "type": "Hash160" + }, + { + "name": "to", + "type": "Hash160" + }, + { + "name": "amount", + "type": "Integer" + } + ] + } + ] + }, + "permissions": [ + { + "contract": "*", + "methods": "*" + } + ], + "trusts": [], + "extra": null + } + } + } + }, + { + "Name": "getcontractstateasync", + "Request": { + "jsonrpc": "2.0", + "id": 1, + "method": "getcontractstate", + "params": [ "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5" ] + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "id": -3, + "updatecounter": 0, + "hash": "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5", + "nef": { + "magic": 860243278, + "compiler": "neo-core-v3.0", + "source": "", + "tokens": [], + "script": "AP1BGvd7Zw==", + "checksum": 3921333105 + }, + "manifest": { + "name": "NeoToken", + "groups": [], + "features": {}, + "supportedstandards": [ + "NEP-17" + ], + "abi": { + "methods": [ + { + "name": "balanceOf", + "parameters": [ + { + "name": "account", + "type": "Hash160" + } + ], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "decimals", + "parameters": [], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "getCandidates", + "parameters": [], + "returntype": "Array", + "offset": 0, + "safe": true + }, + { + "name": "getCommittee", + "parameters": [], + "returntype": "Array", + "offset": 0, + "safe": true + }, + { + "name": "getGasPerBlock", + "parameters": [], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "getNextBlockValidators", + "parameters": [], + "returntype": "Array", + "offset": 0, + "safe": true + }, + { + "name": "registerCandidate", + "parameters": [ + { + "name": "pubkey", + "type": "ByteArray" + } + ], + "returntype": "Boolean", + "offset": 0, + "safe": false + }, + { + "name": "setGasPerBlock", + "parameters": [ + { + "name": "gasPerBlock", + "type": "Integer" + } + ], + "returntype": "Boolean", + "offset": 0, + "safe": false + }, + { + "name": "symbol", + "parameters": [], + "returntype": "String", + "offset": 0, + "safe": true + }, + { + "name": "totalSupply", + "parameters": [], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "transfer", + "parameters": [ + { + "name": "from", + "type": "Hash160" + }, + { + "name": "to", + "type": "Hash160" + }, + { + "name": "amount", + "type": "Integer" + }, + { + "name": "data", + "type": "Any" + } + ], + "returntype": "Boolean", + "offset": 0, + "safe": false + }, + { + "name": "unclaimedGas", + "parameters": [ + { + "name": "account", + "type": "Hash160" + }, + { + "name": "end", + "type": "Integer" + } + ], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "unregisterCandidate", + "parameters": [ + { + "name": "pubkey", + "type": "ByteArray" + } + ], + "returntype": "Boolean", + "offset": 0, + "safe": false + }, + { + "name": "vote", + "parameters": [ + { + "name": "account", + "type": "Hash160" + }, + { + "name": "voteTo", + "type": "ByteArray" + } + ], + "returntype": "Boolean", + "offset": 0, + "safe": false + } + ], + "events": [ + { + "name": "Transfer", + "parameters": [ + { + "name": "from", + "type": "Hash160" + }, + { + "name": "to", + "type": "Hash160" + }, + { + "name": "amount", + "type": "Integer" + } + ] + } + ] + }, + "permissions": [ + { + "contract": "*", + "methods": "*" + } + ], + "trusts": [], + "extra": null + } + } + } + }, + { + "Name": "getnativecontractsasync", + "Request": { + "jsonrpc": "2.0", + "id": 1, + "method": "getnativecontracts", + "params": [] + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "id": -1, + "updatecounter": 0, + "hash": "0xa501d7d7d10983673b61b7a2d3a813b36f9f0e43", + "nef": { + "magic": 860243278, + "compiler": "neo-core-v3.0", + "source": "", + "tokens": [], + "script": "D0Ea93tn", + "checksum": 3516775561 + }, + "manifest": { + "name": "ContractManagement", + "groups": [], + "features": {}, + "supportedstandards": [], + "abi": { + "methods": [ + { + "name": "deploy", + "parameters": [ + { + "name": "nefFile", + "type": "ByteArray" + }, + { + "name": "manifest", + "type": "ByteArray" + } + ], + "returntype": "Array", + "offset": 0, + "safe": false + }, + { + "name": "deploy", + "parameters": [ + { + "name": "nefFile", + "type": "ByteArray" + }, + { + "name": "manifest", + "type": "ByteArray" + }, + { + "name": "data", + "type": "Any" + } + ], + "returntype": "Array", + "offset": 0, + "safe": false + }, + { + "name": "destroy", + "parameters": [], + "returntype": "Void", + "offset": 0, + "safe": false + }, + { + "name": "getContract", + "parameters": [ + { + "name": "hash", + "type": "Hash160" + } + ], + "returntype": "Array", + "offset": 0, + "safe": true + }, + { + "name": "getMinimumDeploymentFee", + "parameters": [], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "setMinimumDeploymentFee", + "parameters": [ + { + "name": "value", + "type": "Integer" + } + ], + "returntype": "Void", + "offset": 0, + "safe": false + }, + { + "name": "update", + "parameters": [ + { + "name": "nefFile", + "type": "ByteArray" + }, + { + "name": "manifest", + "type": "ByteArray" + } + ], + "returntype": "Void", + "offset": 0, + "safe": false + }, + { + "name": "update", + "parameters": [ + { + "name": "nefFile", + "type": "ByteArray" + }, + { + "name": "manifest", + "type": "ByteArray" + }, + { + "name": "data", + "type": "Any" + } + ], + "returntype": "Void", + "offset": 0, + "safe": false + } + ], + "events": [ + { + "name": "Deploy", + "parameters": [ + { + "name": "Hash", + "type": "Hash160" + } + ] + }, + { + "name": "Update", + "parameters": [ + { + "name": "Hash", + "type": "Hash160" + } + ] + }, + { + "name": "Destroy", + "parameters": [ + { + "name": "Hash", + "type": "Hash160" + } + ] + } + ] + }, + "permissions": [ + { + "contract": "*", + "methods": "*" + } + ], + "trusts": [], + "extra": null + } + }, + { + "id": -2, + "updatecounter": 1, + "hash": "0x971d69c6dd10ce88e7dfffec1dc603c6125a8764", + "nef": { + "magic": 860243278, + "compiler": "neo-core-v3.0", + "source": "", + "tokens": [], + "script": "AP5BGvd7Zw==", + "checksum": 3395482975 + }, + "manifest": { + "name": "LedgerContract", + "groups": [], + "features": {}, + "supportedstandards": [], + "abi": { + "methods": [ + { + "name": "currentHash", + "parameters": [], + "returntype": "Hash256", + "offset": 0, + "safe": true + }, + { + "name": "currentIndex", + "parameters": [], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "getBlock", + "parameters": [ + { + "name": "indexOrHash", + "type": "ByteArray" + } + ], + "returntype": "Array", + "offset": 0, + "safe": true + }, + { + "name": "getTransaction", + "parameters": [ + { + "name": "hash", + "type": "Hash256" + } + ], + "returntype": "Array", + "offset": 0, + "safe": true + }, + { + "name": "getTransactionFromBlock", + "parameters": [ + { + "name": "blockIndexOrHash", + "type": "ByteArray" + }, + { + "name": "txIndex", + "type": "Integer" + } + ], + "returntype": "Array", + "offset": 0, + "safe": true + }, + { + "name": "getTransactionHeight", + "parameters": [ + { + "name": "hash", + "type": "Hash256" + } + ], + "returntype": "Integer", + "offset": 0, + "safe": true + } + ], + "events": [] + }, + "permissions": [ + { + "contract": "*", + "methods": "*" + } + ], + "trusts": [], + "extra": null + } + }, + { + "id": -3, + "updatecounter": 0, + "hash": "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5", + "nef": { + "magic": 860243278, + "compiler": "neo-core-v3.0", + "source": "", + "tokens": [], + "script": "AP1BGvd7Zw==", + "checksum": 3921333105 + }, + "manifest": { + "name": "NeoToken", + "groups": [], + "features": {}, + "supportedstandards": [ + "NEP-17" + ], + "abi": { + "methods": [ + { + "name": "balanceOf", + "parameters": [ + { + "name": "account", + "type": "Hash160" + } + ], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "decimals", + "parameters": [], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "getCandidates", + "parameters": [], + "returntype": "Array", + "offset": 0, + "safe": true + }, + { + "name": "getCommittee", + "parameters": [], + "returntype": "Array", + "offset": 0, + "safe": true + }, + { + "name": "getGasPerBlock", + "parameters": [], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "getNextBlockValidators", + "parameters": [], + "returntype": "Array", + "offset": 0, + "safe": true + }, + { + "name": "registerCandidate", + "parameters": [ + { + "name": "pubkey", + "type": "ByteArray" + } + ], + "returntype": "Boolean", + "offset": 0, + "safe": false + }, + { + "name": "setGasPerBlock", + "parameters": [ + { + "name": "gasPerBlock", + "type": "Integer" + } + ], + "returntype": "Boolean", + "offset": 0, + "safe": false + }, + { + "name": "symbol", + "parameters": [], + "returntype": "String", + "offset": 0, + "safe": true + }, + { + "name": "totalSupply", + "parameters": [], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "transfer", + "parameters": [ + { + "name": "from", + "type": "Hash160" + }, + { + "name": "to", + "type": "Hash160" + }, + { + "name": "amount", + "type": "Integer" + }, + { + "name": "data", + "type": "Any" + } + ], + "returntype": "Boolean", + "offset": 0, + "safe": false + }, + { + "name": "unclaimedGas", + "parameters": [ + { + "name": "account", + "type": "Hash160" + }, + { + "name": "end", + "type": "Integer" + } + ], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "unregisterCandidate", + "parameters": [ + { + "name": "pubkey", + "type": "ByteArray" + } + ], + "returntype": "Boolean", + "offset": 0, + "safe": false + }, + { + "name": "vote", + "parameters": [ + { + "name": "account", + "type": "Hash160" + }, + { + "name": "voteTo", + "type": "ByteArray" + } + ], + "returntype": "Boolean", + "offset": 0, + "safe": false + } + ], + "events": [ + { + "name": "Transfer", + "parameters": [ + { + "name": "from", + "type": "Hash160" + }, + { + "name": "to", + "type": "Hash160" + }, + { + "name": "amount", + "type": "Integer" + } + ] + } + ] + }, + "permissions": [ + { + "contract": "*", + "methods": "*" + } + ], + "trusts": [], + "extra": null + } + }, + { + "id": -4, + "updatecounter": 0, + "hash": "0xd2a4cff31913016155e38e474a2c06d08be276cf", + "nef": { + "magic": 860243278, + "compiler": "neo-core-v3.0", + "source": "", + "tokens": [], + "script": "APxBGvd7Zw==", + "checksum": 3155977747 + }, + "manifest": { + "name": "GasToken", + "groups": [], + "features": {}, + "supportedstandards": [ + "NEP-17" + ], + "abi": { + "methods": [ + { + "name": "balanceOf", + "parameters": [ + { + "name": "account", + "type": "Hash160" + } + ], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "decimals", + "parameters": [], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "symbol", + "parameters": [], + "returntype": "String", + "offset": 0, + "safe": true + }, + { + "name": "totalSupply", + "parameters": [], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "transfer", + "parameters": [ + { + "name": "from", + "type": "Hash160" + }, + { + "name": "to", + "type": "Hash160" + }, + { + "name": "amount", + "type": "Integer" + }, + { + "name": "data", + "type": "Any" + } + ], + "returntype": "Boolean", + "offset": 0, + "safe": false + } + ], + "events": [ + { + "name": "Transfer", + "parameters": [ + { + "name": "from", + "type": "Hash160" + }, + { + "name": "to", + "type": "Hash160" + }, + { + "name": "amount", + "type": "Integer" + } + ] + } + ] + }, + "permissions": [ + { + "contract": "*", + "methods": "*" + } + ], + "trusts": [], + "extra": null + } + }, + { + "id": -5, + "updatecounter": 0, + "hash": "0x79bcd398505eb779df6e67e4be6c14cded08e2f2", + "nef": { + "magic": 860243278, + "compiler": "neo-core-v3.0", + "source": "", + "tokens": [], + "script": "APtBGvd7Zw==", + "checksum": 1136340263 + }, + "manifest": { + "name": "PolicyContract", + "groups": [], + "features": {}, + "supportedstandards": [], + "abi": { + "methods": [ + { + "name": "blockAccount", + "parameters": [ + { + "name": "account", + "type": "Hash160" + } + ], + "returntype": "Boolean", + "offset": 0, + "safe": false + }, + { + "name": "getExecFeeFactor", + "parameters": [], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "getFeePerByte", + "parameters": [], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "getMaxBlockSize", + "parameters": [], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "getMaxBlockSystemFee", + "parameters": [], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "getMaxTransactionsPerBlock", + "parameters": [], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "getStoragePrice", + "parameters": [], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "isBlocked", + "parameters": [ + { + "name": "account", + "type": "Hash160" + } + ], + "returntype": "Boolean", + "offset": 0, + "safe": true + }, + { + "name": "setExecFeeFactor", + "parameters": [ + { + "name": "value", + "type": "Integer" + } + ], + "returntype": "Boolean", + "offset": 0, + "safe": false + }, + { + "name": "setFeePerByte", + "parameters": [ + { + "name": "value", + "type": "Integer" + } + ], + "returntype": "Boolean", + "offset": 0, + "safe": false + }, + { + "name": "setMaxBlockSize", + "parameters": [ + { + "name": "value", + "type": "Integer" + } + ], + "returntype": "Boolean", + "offset": 0, + "safe": false + }, + { + "name": "setMaxBlockSystemFee", + "parameters": [ + { + "name": "value", + "type": "Integer" + } + ], + "returntype": "Boolean", + "offset": 0, + "safe": false + }, + { + "name": "setMaxTransactionsPerBlock", + "parameters": [ + { + "name": "value", + "type": "Integer" + } + ], + "returntype": "Boolean", + "offset": 0, + "safe": false + }, + { + "name": "setStoragePrice", + "parameters": [ + { + "name": "value", + "type": "Integer" + } + ], + "returntype": "Boolean", + "offset": 0, + "safe": false + }, + { + "name": "unblockAccount", + "parameters": [ + { + "name": "account", + "type": "Hash160" + } + ], + "returntype": "Boolean", + "offset": 0, + "safe": false + } + ], + "events": [] + }, + "permissions": [ + { + "contract": "*", + "methods": "*" + } + ], + "trusts": [], + "extra": null + } + }, + { + "id": -6, + "updatecounter": 0, + "hash": "0x597b1471bbce497b7809e2c8f10db67050008b02", + "nef": { + "magic": 860243278, + "compiler": "neo-core-v3.0", + "source": "", + "tokens": [], + "script": "APpBGvd7Zw==", + "checksum": 3289425910 + }, + "manifest": { + "name": "RoleManagement", + "groups": [], + "features": {}, + "supportedstandards": [], + "abi": { + "methods": [ + { + "name": "designateAsRole", + "parameters": [ + { + "name": "role", + "type": "Integer" + }, + { + "name": "nodes", + "type": "Array" + } + ], + "returntype": "Void", + "offset": 0, + "safe": false + }, + { + "name": "getDesignatedByRole", + "parameters": [ + { + "name": "role", + "type": "Integer" + }, + { + "name": "index", + "type": "Integer" + } + ], + "returntype": "Array", + "offset": 0, + "safe": true + } + ], + "events": [] + }, + "permissions": [ + { + "contract": "*", + "methods": "*" + } + ], + "trusts": [], + "extra": null + } + }, + { + "id": -7, + "updatecounter": 0, + "hash": "0x8dc0e742cbdfdeda51ff8a8b78d46829144c80ee", + "nef": { + "magic": 860243278, + "compiler": "neo-core-v3.0", + "source": "", + "tokens": [], + "script": "APlBGvd7Zw==", + "checksum": 3902663397 + }, + "manifest": { + "name": "OracleContract", + "groups": [], + "features": {}, + "supportedstandards": [], + "abi": { + "methods": [ + { + "name": "finish", + "parameters": [], + "returntype": "Void", + "offset": 0, + "safe": false + }, + { + "name": "request", + "parameters": [ + { + "name": "url", + "type": "String" + }, + { + "name": "filter", + "type": "String" + }, + { + "name": "callback", + "type": "String" + }, + { + "name": "userData", + "type": "Any" + }, + { + "name": "gasForResponse", + "type": "Integer" + } + ], + "returntype": "Void", + "offset": 0, + "safe": false + }, + { + "name": "verify", + "parameters": [], + "returntype": "Boolean", + "offset": 0, + "safe": true + } + ], + "events": [ + { + "name": "OracleRequest", + "parameters": [ + { + "name": "Id", + "type": "Integer" + }, + { + "name": "RequestContract", + "type": "Hash160" + }, + { + "name": "Url", + "type": "String" + }, + { + "name": "Filter", + "type": "String" + } + ] + }, + { + "name": "OracleResponse", + "parameters": [ + { + "name": "Id", + "type": "Integer" + }, + { + "name": "OriginalTx", + "type": "Hash256" + } + ] + } + ] + }, + "permissions": [ + { + "contract": "*", + "methods": "*" + } + ], + "trusts": [], + "extra": null + } + }, + { + "id": -8, + "updatecounter": 0, + "hash": "0xa2b524b68dfe43a9d56af84f443c6b9843b8028c", + "nef": { + "magic": 860243278, + "compiler": "neo-core-v3.0", + "source": "", + "tokens": [], + "script": "APhBGvd7Zw==", + "checksum": 3740064217 + }, + "manifest": { + "name": "NameService", + "groups": [], + "features": {}, + "supportedstandards": [], + "abi": { + "methods": [ + { + "name": "addRoot", + "parameters": [ + { + "name": "root", + "type": "String" + } + ], + "returntype": "Void", + "offset": 0, + "safe": false + }, + { + "name": "balanceOf", + "parameters": [ + { + "name": "owner", + "type": "Hash160" + } + ], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "decimals", + "parameters": [], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "deleteRecord", + "parameters": [ + { + "name": "name", + "type": "String" + }, + { + "name": "type", + "type": "Integer" + } + ], + "returntype": "Void", + "offset": 0, + "safe": false + }, + { + "name": "getPrice", + "parameters": [], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "getRecord", + "parameters": [ + { + "name": "name", + "type": "String" + }, + { + "name": "type", + "type": "Integer" + } + ], + "returntype": "String", + "offset": 0, + "safe": true + }, + { + "name": "isAvailable", + "parameters": [ + { + "name": "name", + "type": "String" + } + ], + "returntype": "Boolean", + "offset": 0, + "safe": true + }, + { + "name": "ownerOf", + "parameters": [ + { + "name": "tokenId", + "type": "ByteArray" + } + ], + "returntype": "Hash160", + "offset": 0, + "safe": true + }, + { + "name": "properties", + "parameters": [ + { + "name": "tokenId", + "type": "ByteArray" + } + ], + "returntype": "Map", + "offset": 0, + "safe": true + }, + { + "name": "register", + "parameters": [ + { + "name": "name", + "type": "String" + }, + { + "name": "owner", + "type": "Hash160" + } + ], + "returntype": "Boolean", + "offset": 0, + "safe": false + }, + { + "name": "renew", + "parameters": [ + { + "name": "name", + "type": "String" + } + ], + "returntype": "Integer", + "offset": 0, + "safe": false + }, + { + "name": "resolve", + "parameters": [ + { + "name": "name", + "type": "String" + }, + { + "name": "type", + "type": "Integer" + } + ], + "returntype": "String", + "offset": 0, + "safe": true + }, + { + "name": "setAdmin", + "parameters": [ + { + "name": "name", + "type": "String" + }, + { + "name": "admin", + "type": "Hash160" + } + ], + "returntype": "Void", + "offset": 0, + "safe": false + }, + { + "name": "setPrice", + "parameters": [ + { + "name": "price", + "type": "Integer" + } + ], + "returntype": "Void", + "offset": 0, + "safe": false + }, + { + "name": "setRecord", + "parameters": [ + { + "name": "name", + "type": "String" + }, + { + "name": "type", + "type": "Integer" + }, + { + "name": "data", + "type": "String" + } + ], + "returntype": "Void", + "offset": 0, + "safe": false + }, + { + "name": "symbol", + "parameters": [], + "returntype": "String", + "offset": 0, + "safe": true + }, + { + "name": "tokensOf", + "parameters": [ + { + "name": "owner", + "type": "Hash160" + } + ], + "returntype": "Any", + "offset": 0, + "safe": true + }, + { + "name": "totalSupply", + "parameters": [], + "returntype": "Integer", + "offset": 0, + "safe": true + }, + { + "name": "transfer", + "parameters": [ + { + "name": "to", + "type": "Hash160" + }, + { + "name": "tokenId", + "type": "ByteArray" + } + ], + "returntype": "Boolean", + "offset": 0, + "safe": false + } + ], + "events": [ + { + "name": "Transfer", + "parameters": [ + { + "name": "from", + "type": "Hash160" + }, + { + "name": "to", + "type": "Hash160" + }, + { + "name": "amount", + "type": "Integer" + }, + { + "name": "tokenId", + "type": "ByteArray" + } + ] + } + ] + }, + "permissions": [ + { + "contract": "*", + "methods": "*" + } + ], + "trusts": [], + "extra": null + } + } + ] + } + }, + { + "Name": "getrawmempoolasync", + "Request": { + "jsonrpc": "2.0", + "method": "getrawmempool", + "params": [], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ "0x9786cce0dddb524c40ddbdd5e31a41ed1f6b5c8a683c122f627ca4a007a7cf4e", "0xb488ad25eb474f89d5ca3f985cc047ca96bc7373a6d3da8c0f192722896c1cd7" ] + } + }, + { + "Name": "getrawmempoolbothasync", + "Request": { + "jsonrpc": "2.0", + "method": "getrawmempool", + "params": [ true ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "height": 2846, + "verified": [ "0x9786cce0dddb524c40ddbdd5e31a41ed1f6b5c8a683c122f627ca4a007a7cf4e" ], + "unverified": [ "0xb488ad25eb474f89d5ca3f985cc047ca96bc7373a6d3da8c0f192722896c1cd7" ] + } + } + }, + { + "Name": "getrawtransactionhexasync", + "Request": { + "jsonrpc": "2.0", + "method": "getrawtransaction", + "params": [ "0x0cfd49c48306f9027dc71585589b6456bcc53567c359fb7858eabca482186b78" ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": "004cdec1396925aa554712439a9c613ba114efa3fac23ddbca00e1f50500000000466a130000000000311d2000005d030010a5d4e80000000c149903b0c3d292988febe5f306a02f654ea2eb16290c146925aa554712439a9c613ba114efa3fac23ddbca13c00c087472616e736665720c143b7d3711c6f0ccf9b1dca903d1bfa1d896f1238c41627d5b523901420c401f85b40d7fa12164aa1d4d18b06ca470f2c89572dc5b901ab1667faebb587cf536454b98a09018adac72376c5e7c5d164535155b763564347aa47b69aa01b3cc290c2103aa052fbcb8e5b33a4eefd662536f8684641f04109f1d5e69cdda6f084890286a0b410a906ad4" + } + }, + { + "Name": "getrawtransactionasync", + "Request": { + "jsonrpc": "2.0", + "method": "getrawtransaction", + "params": [ "0xc97cc05c790a844f05f582d80952c4ced3894cbe6d96a74f3e5589d741372dd4", true ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "hash": "0x99eaba3e230702d428ce6bfb4a2dceba6d4cd441f9ca1b7bfe2a418926ae40ab", + "size": 252, + "version": 0, + "nonce": 969006668, + "sender": "NikvsLcNP1jWhrFPrfS3n4spEASgdNYTG2", + "sysfee": "100000000", + "netfee": "1272390", + "validuntilblock": 2104625, + "signers": [ + { + "account": "0xe19de267a37a71734478f512b3e92c79fc3695fa", + "scopes": "CalledByEntry" + } + ], + "attributes": [], + "script": "AwAQpdToAAAADBSZA7DD0pKYj\u002Bvl8wagL2VOousWKQwUaSWqVUcSQ5qcYTuhFO\u002Bj\u002BsI928oTwAwIdHJhbnNmZXIMFDt9NxHG8Mz5sdypA9G/odiW8SOMQWJ9W1I5", + "witnesses": [ + { + "invocation": "DEAfhbQNf6EhZKodTRiwbKRw8siVctxbkBqxZn\u002Buu1h89TZFS5igkBitrHI3bF58XRZFNRVbdjVkNHqke2mqAbPM", + "verification": "DCEDqgUvvLjlszpO79ZiU2\u002BGhGQfBBCfHV5pzdpvCEiQKGoLQQqQatQ=" + } + ], + "blockhash": "0xc1ed259e394c9cd93c1e0eb1e0f144c0d10da64861a24c0084f0d98270b698f1", + "confirmations": 643, + "blocktime": 1579417249620, + "vmstate": "HALT" + } + } + }, + { + "Name": "getstorageasync", + "Request": { + "jsonrpc": "2.0", + "method": "getstorage", + "params": [ "0x8c23f196d8a1bfd103a9dcb1f9ccf0c611377d3b", "146925aa554712439a9c613ba114efa3fac23ddbca" ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": "410121064c5d11a2a700" + } + }, + { + "Name": "getstorageasync", + "Request": { + "jsonrpc": "2.0", + "method": "getstorage", + "params": [ -2, "146925aa554712439a9c613ba114efa3fac23ddbca" ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": "410121064c5d11a2a700" + } + }, + { + "Name": "gettransactionheightasync", + "Request": { + "jsonrpc": "2.0", + "method": "gettransactionheight", + "params": [ "0x0cfd49c48306f9027dc71585589b6456bcc53567c359fb7858eabca482186b78" ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": 2226 + } + }, + { + "Name": "getnextblockvalidatorsasync", + "Request": { + "jsonrpc": "2.0", + "method": "getnextblockvalidators", + "params": [], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "publickey": "03aa052fbcb8e5b33a4eefd662536f8684641f04109f1d5e69cdda6f084890286a", + "votes": "0" + } + ] + } + }, + + + { + "Name": "getconnectioncountasync", + "Request": { + "jsonrpc": "2.0", + "method": "getconnectioncount", + "params": [], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": 0 + } + }, + { + "Name": "getpeersasync", + "Request": { + "jsonrpc": "2.0", + "method": "getpeers", + "params": [], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "unconnected": [ + { + "address": "::ffff:70.73.16.236", + "port": 10333 + } + ], + "bad": [], + "connected": [ + { + "address": "::ffff:139.219.106.33", + "port": 10333 + }, + { + "address": "::ffff:47.88.53.224", + "port": 10333 + } + ] + } + } + }, + { + "Name": "getversionasync", + "Request": { + "jsonrpc": "2.0", + "method": "getversion", + "params": [], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "network": 0, + "tcpport": 20333, + "nonce": 592651621, + "useragent": "/Neo:3.0.0-rc1/", + "protocol": { + "network": 0, + "validatorscount": 0, + "msperblock": 15000, + "maxvaliduntilblockincrement": 1, + "maxtraceableblocks": 1, + "addressversion": 0, + "maxtransactionsperblock": 0, + "memorypoolmaxtransactions": 0, + "initialgasdistribution": 0, + "hardforks": [ + { + "name": "Aspidochelone", + "blockheight": 0 + } + ] + } + } + } + }, + { + "Name": "sendrawtransactionasync", + "Request": { + "jsonrpc": "2.0", + "method": "sendrawtransaction", + "params": [ "ANIHn05ujtUAAAAAACYcEwAAAAAAQkEAAAEKo4e1Ppa3mJpjFDGgVt0fQKBC9gEAXQMAyBeoBAAAAAwUzViuz9M1vh6z0xHh3IAJY9/XLZ8MFAqjh7U+lreYmmMUMaBW3R9AoEL2E8AMCHRyYW5zZmVyDBSlB7dGdv/td+dUuG7NmQnwus08ukFifVtSOAFCDEDh8zgTrGUXyzVX60wBCMyajNRfzFRiEPAe8CgGQ10bA2C3fnVz68Gw+Amgn5gmvuNfYKgWQ/W68Km1bYUPlnEYKQwhA86j4vgfGvk1ItKe3k8kofC+3q1ykzkdM4gPVHXZeHjJC0GVRA14" ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "hash": "0x4d47255ff5564aaa73855068c3574f8f28e2bb18c7fb7256e58ae51fab44c9bc" + } + } + }, + { + "Name": "submitblockasync", + "Request": { + "jsonrpc": "2.0", + "method": "submitblock", + "params": [ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI+JfEVZZd6cjX2qJADFSuzRR40IzeV3K1zS9Q2wqetqI6hnvVQEAAAAAAAD6lrDvowCyjK9dBALCmE1fvMuahQEAARECAB2sK3wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKYeUuiB1BN05kD4Gc0RjMFTshpwAABUESPn/oAQABEQ==" ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "hash": "0xa11c9d14748f967178fe22fdcfb829354ae6ccb86824675e147cb128f16d8171" + } + } + }, + + + { + "Name": "invokefunctionasync", + "Request": { + "jsonrpc": "2.0", + "method": "invokefunction", + "params": [ + "0x8c23f196d8a1bfd103a9dcb1f9ccf0c611377d3b", + "balanceOf", + [ + { + "type": "Hash160", + "value": "91b83e96f2a7c4fdf0c1688441ec61986c7cae26" + } + ] + ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "script": "0c1426ae7c6c9861ec418468c1f0fdc4a7f2963eb89111c00c0962616c616e63654f660c143b7d3711c6f0ccf9b1dca903d1bfa1d896f1238c41627d5b52", + "state": "HALT", + "gasconsumed": "2007570", + "stack": [ + { + "type": "Integer", + "value": "0" + } + ], + "tx": "00d1eb88136925aa554712439a9c613ba114efa3fac23ddbca00e1f50500000000269f1200000000004520200000003e0c1426ae7c6c9861ec418468c1f0fdc4a7f2963eb89111c00c0962616c616e63654f660c143b7d3711c6f0ccf9b1dca903d1bfa1d896f1238c41627d5b5201420c40794c91299bba340ea2505c777d15ca898f75bcce686461066a2b8018cc1de114a122dcdbc77b447ac7db5fb1584f1533b164fbc8f30ddf5bd6acf016a125e983290c2103aa052fbcb8e5b33a4eefd662536f8684641f04109f1d5e69cdda6f084890286a0b410a906ad4" + } + } + }, + { + "Name": "invokescriptasync", + "Request": { + "jsonrpc": "2.0", + "method": "invokescript", + "params": [ "EMMMBG5hbWUMFDt9NxHG8Mz5sdypA9G/odiW8SOMQWJ9W1IQwwwGc3ltYm9sDBQ7fTcRxvDM+bHcqQPRv6HYlvEjjEFifVtSEMMMCGRlY2ltYWxzDBQ7fTcRxvDM+bHcqQPRv6HYlvEjjEFifVtSEMMMC3RvdGFsU3VwcGx5DBQ7fTcRxvDM+bHcqQPRv6HYlvEjjEFifVtS" ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "script": "EMMMBG5hbWUMFDt9NxHG8Mz5sdypA9G/odiW8SOMQWJ9W1IQwwwGc3ltYm9sDBQ7fTcRxvDM+bHcqQPRv6HYlvEjjEFifVtSEMMMCGRlY2ltYWxzDBQ7fTcRxvDM+bHcqQPRv6HYlvEjjEFifVtSEMMMC3RvdGFsU3VwcGx5DBQ7fTcRxvDM+bHcqQPRv6HYlvEjjEFifVtS", + "state": "HALT", + "gasconsumed": "5061560", + "stack": [ + { + "type": "Array", + "value": [ + { + "type": "ByteString", + "value": "dGVzdA==" + }, + { + "type": "InteropInterface" + }, + { + "type": "Integer", + "value": "1" + }, + { + "type": "Buffer", + "value": "CAwiNQw=" + }, + { + "type": "Array", + "value": [ + { + "type": "ByteString", + "value": "YmI=" + }, + { + "type": "ByteString", + "value": "Y2Mw" + } + ] + }, + { + "type": "Map", + "value": [ + { + "key": { + "type": "Integer", + "value": "2" + }, + "value": { + "type": "Integer", + "value": "12" + } + }, + { + "key": { + "type": "Integer", + "value": "0" + }, + "value": { + "type": "Integer", + "value": "24" + } + } + ] + } + ] + } + ], + "tx": "00769d16556925aa554712439a9c613ba114efa3fac23ddbca00e1f505000000009e021400000000005620200000009910c30c046e616d650c143b7d3711c6f0ccf9b1dca903d1bfa1d896f1238c41627d5b5210c30c0673796d626f6c0c143b7d3711c6f0ccf9b1dca903d1bfa1d896f1238c41627d5b5210c30c08646563696d616c730c143b7d3711c6f0ccf9b1dca903d1bfa1d896f1238c41627d5b5210c30c0b746f74616c537570706c790c143b7d3711c6f0ccf9b1dca903d1bfa1d896f1238c41627d5b5201420c40c848d0fcbf5e6a820508242ea8b7ccbeed3caefeed5db570537279c2154f7cfd8b0d8f477f37f4e6ca912935b732684d57c455dff7aa525ad4ab000931f22208290c2103aa052fbcb8e5b33a4eefd662536f8684641f04109f1d5e69cdda6f084890286a0b410a906ad4" + } + } + }, + + { + "Name": "getunclaimedgasasync", + "Request": { + "jsonrpc": "2.0", + "method": "getunclaimedgas", + "params": [ "NPvKVTGZapmFWABLsyvfreuqn73jCjJtN1" ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "unclaimed": "735870007400", + "address": "NPvKVTGZapmFWABLsyvfreuqn73jCjJtN1" + } + } + }, + + { + "Name": "listpluginsasync", + "Request": { + "jsonrpc": "2.0", + "method": "listplugins", + "params": [], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "name": "ApplicationLogs", + "version": "3.0.0.0", + "interfaces": [ + "IPersistencePlugin" + ] + }, + { + "name": "LevelDBStore", + "version": "3.0.0.0", + "interfaces": [ + "IStoragePlugin" + ] + }, + { + "name": "RpcNep17Tracker", + "version": "3.0.0.0", + "interfaces": [ + "IPersistencePlugin" + ] + }, + { + "name": "RpcServer", + "version": "3.0.0.0", + "interfaces": [] + } + ] + } + }, + { + "Name": "validateaddressasync", + "Request": { + "jsonrpc": "2.0", + "method": "validateaddress", + "params": [ "NZs2zXSPuuv9ZF6TDGSWT1RBmE8rfGj7UW" ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "address": "NZs2zXSPuuv9ZF6TDGSWT1RBmE8rfGj7UW", + "isvalid": true + } + } + }, + + + { + "Name": "closewalletasync", + "Request": { + "jsonrpc": "2.0", + "method": "closewallet", + "params": [], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": true + } + }, + { + "Name": "dumpprivkeyasync", + "Request": { + "jsonrpc": "2.0", + "method": "dumpprivkey", + "params": [ "NVVwFw6XyhtRCFQ8SpUTMdPyYt4Vd9A1XQ" ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": "KyoYyZpoccbR6KZ25eLzhMTUxREwCpJzDsnuodGTKXSG8fDW9t7x" + } + }, + { + "Name": "invokescriptasync", + "Request": { + "jsonrpc": "2.0", + "id": 1, + "method": "invokescript", + "params": [ + "EMAfDAhkZWNpbWFscwwU++3+LtIiZZK2SMTal7nJzV3BpqZBYn1bUg==" + ] + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "script": "HxDDDAhkZWNpbWFscwwU++3+LtIiZZK2SMTal7nJzV3BpqZB7vQM2w==", + "state": "HALT", + "gasconsumed": "999180", + "exception": null, + "stack": [ + { + "type": "Integer", + "value": "8" + } + ] + } + } + }, + { + "Name": "invokescriptasync", + "Request": { + "jsonrpc": "2.0", + "id": 1, + "method": "invokescript", + "params": [ + "wh8MCGRlY2ltYWxzDBTPduKL0AYsSkeO41VhARMZ88+k0kFifVtS" + ] + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "script": "EBEfDAhkZWNpbWFscwwU++3+LtIiZZK2SMTal7nJzV3BpqZBYn1bUg==", + "state": "HALT", + "gasconsumed": "999180", + "exception": null, + "stack": [ + { + "type": "Integer", + "value": "8" + } + ] + } + } + }, + { + "Name": "getnewaddressasync", + "Request": { + "jsonrpc": "2.0", + "method": "getnewaddress", + "params": [], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": "NXpCs9kcDkPvfyAobNYmFg8yfRZaDopDbf" + } + }, + { + "Name": "getwalletbalanceasync", + "Request": { + "jsonrpc": "2.0", + "method": "getwalletbalance", + "params": [ "0xd2a4cff31913016155e38e474a2c06d08be276cf" ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "balance": "3001101329992600" + } + } + }, + { + "Name": "getwalletunclaimedgasasync", + "Request": { + "jsonrpc": "2.0", + "method": "getwalletunclaimedgas", + "params": [], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": "735870007400" + } + }, + { + "Name": "importprivkeyasync", + "Request": { + "jsonrpc": "2.0", + "method": "importprivkey", + "params": [ "KyoYyZpoccbR6KZ25eLzhMTUxREwCpJzDsnuodGTKXSG8fDW9t7x" ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "address": "NVVwFw6XyhtRCFQ8SpUTMdPyYt4Vd9A1XQ", + "haskey": true, + "label": null, + "watchonly": false + } + } + }, + { + "Name": "listaddressasync", + "Request": { + "jsonrpc": "2.0", + "method": "listaddress", + "params": [], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": [ + { + "address": "NVVwFw6XyhtRCFQ8SpUTMdPyYt4Vd9A1XQ", + "haskey": true, + "label": null, + "watchonly": false + }, + { + "address": "NZs2zXSPuuv9ZF6TDGSWT1RBmE8rfGj7UW", + "haskey": true, + "label": null, + "watchonly": false + } + ] + } + }, + { + "Name": "openwalletasync", + "Request": { + "jsonrpc": "2.0", + "method": "openwallet", + "params": [ "D:\\temp\\3.json", "1111" ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": true + } + }, + { + "Name": "sendfromasync", + "Request": { + "jsonrpc": "2.0", + "method": "sendfrom", + "params": [ "0x8c23f196d8a1bfd103a9dcb1f9ccf0c611377d3b", "NVVwFw6XyhtRCFQ8SpUTMdPyYt4Vd9A1XQ", "NZs2zXSPuuv9ZF6TDGSWT1RBmE8rfGj7UW", "100.123" ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "hash": "0x035facc3be1fc57da1690e3d2f8214f449d368437d8557ffabb2d408caf9ad76", + "size": 272, + "version": 0, + "nonce": 1553700339, + "sender": "NVVwFw6XyhtRCFQ8SpUTMdPyYt4Vd9A1XQ", + "sysfee": "100000000", + "netfee": "1272390", + "validuntilblock": 2105487, + "attributes": [], + "cosigners": [ + { + "account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", + "scopes": "CalledByEntry" + } + ], + "script": "A+CSx1QCAAAADBSZA7DD0pKYj+vl8wagL2VOousWKQwUaSWqVUcSQ5qcYTuhFO+j+sI928oTwAwIdHJhbnNmZXIMFDt9NxHG8Mz5sdypA9G/odiW8SOMQWJ9W1I5", + "witnesses": [ + { + "invocation": "DEDOA/QF5jYT2TCl9T94fFwAncuBhVhciISaq4fZ3WqGarEoT/0iDo3RIwGjfRW0mm/SV3nAVGEQeZInLqKQ98HX", + "verification": "DCEDqgUvvLjlszpO79ZiU2+GhGQfBBCfHV5pzdpvCEiQKGoLQQqQatQ=" + } + ] + } + } + }, + { + "Name": "sendmanyasync", + "Request": { + "jsonrpc": "2.0", + "method": "sendmany", + "params": [ + "NVVwFw6XyhtRCFQ8SpUTMdPyYt4Vd9A1XQ", + [ + { + "asset": "0x9bde8f209c88dd0e7ca3bf0af0f476cdd8207789", + "value": "10", + "address": "NVVwFw6XyhtRCFQ8SpUTMdPyYt4Vd9A1XQ" + }, + { + "asset": "0x8c23f196d8a1bfd103a9dcb1f9ccf0c611377d3b", + "value": "1.2345", + "address": "NZs2zXSPuuv9ZF6TDGSWT1RBmE8rfGj7UW" + } + ] + ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "hash": "0x542e64a9048bbe1ee565b840c41ccf9b5a1ef11f52e5a6858a523938a20c53ec", + "size": 483, + "version": 0, + "nonce": 34429660, + "sender": "NUMK37TV9yYKbJr1Gufh74nZiM623eBLqX", + "sysfee": "100000000", + "netfee": "2483780", + "validuntilblock": 2105494, + "attributes": [], + "cosigners": [ + { + "account": "0x36d6200fb4c9737c7b552d2b5530ab43605c5869", + "scopes": "CalledByEntry" + }, + { + "account": "0x9a55ca1006e2c359bbc8b9b0de6458abdff98b5c", + "scopes": "CalledByEntry" + } + ], + "script": "GgwUaSWqVUcSQ5qcYTuhFO+j+sI928oMFGlYXGBDqzBVKy1Ve3xzybQPINY2E8AMCHRyYW5zZmVyDBSJdyDYzXb08Aq/o3wO3YicII/em0FifVtSOQKQslsHDBSZA7DD0pKYj+vl8wagL2VOousWKQwUXIv536tYZN6wuci7WcPiBhDKVZoTwAwIdHJhbnNmZXIMFDt9NxHG8Mz5sdypA9G/odiW8SOMQWJ9W1I5", + "witnesses": [ + { + "invocation": "DECOdTEWg1WkuHN0GNV67kwxeuKADyC6TO59vTaU5dK6K1BGt8+EM6L3TdMga4qB2J+Meez8eYwZkSSRubkuvfr9", + "verification": "DCECeiS9CyBqFJwNKzonOs/yzajOraFep4IqFJVxBe6TesULQQqQatQ=" + }, + { + "invocation": "DEB1Laj6lvjoBJLTgE/RdvbJiXOmaKp6eNWDJt+p8kxnW6jbeKoaBRZWfUflqrKV7mZEE2JHA5MxrL5TkRIvsL5K", + "verification": "DCECkXL4gxd936eGEDt3KWfIuAsBsQcfyyBUcS8ggF6lZnwLQQqQatQ=" + } + ] + } + } + }, + { + "Name": "sendtoaddressasync", + "Request": { + "jsonrpc": "2.0", + "method": "sendtoaddress", + "params": [ "0x8c23f196d8a1bfd103a9dcb1f9ccf0c611377d3b", "NVVwFw6XyhtRCFQ8SpUTMdPyYt4Vd9A1XQ", "100.123" ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "hash": "0xee5fc3f57d9f9bc9695c88ecc504444aab622b1680b1cb0848d5b6e39e7fd118", + "size": 381, + "version": 0, + "nonce": 330056065, + "sender": "NUMK37TV9yYKbJr1Gufh74nZiM623eBLqX", + "sysfee": "100000000", + "netfee": "2381780", + "validuntilblock": 2105500, + "attributes": [], + "cosigners": [ + { + "account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", + "scopes": "CalledByEntry" + } + ], + "script": "A+CSx1QCAAAADBRpJapVRxJDmpxhO6EU76P6wj3bygwUaSWqVUcSQ5qcYTuhFO+j+sI928oTwAwIdHJhbnNmZXIMFDt9NxHG8Mz5sdypA9G/odiW8SOMQWJ9W1I5", + "witnesses": [ + { + "invocation": "DECruSKmQKs0Y2cxplKROjPx8HKiyiYrrPn7zaV9zwHPumLzFc8DvgIo2JxmTnJsORyygN/su8mTmSLLb3PesBvY", + "verification": "DCECkXL4gxd936eGEDt3KWfIuAsBsQcfyyBUcS8ggF6lZnwLQQqQatQ=" + }, + { + "invocation": "DECS5npCs5PwsPUAQ01KyHyCev27dt3kDdT1Vi0K8PwnEoSlxYTOGGQCAwaiNEXSyBdBmT6unhZydmFnkezD7qzW", + "verification": "DCEDqgUvvLjlszpO79ZiU2+GhGQfBBCfHV5pzdpvCEiQKGoLQQqQatQ=" + } + ] + } + } + }, + + + { + "Name": "getapplicationlogasync", + "Request": { + "jsonrpc": "2.0", + "method": "getapplicationlog", + "params": [ "0x6ea186fe714b8168ede3b78461db8945c06d867da649852352dbe7cbf1ba3724" ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "blockhash": "0x6ea186fe714b8168ede3b78461db8945c06d867da649852352dbe7cbf1ba3724", + "executions": [ + { + "trigger": "OnPersist", + "vmstate": "HALT", + "gasconsumed": "2031260", + "exception": null, + "stack": [], + "notifications": [ + { + "contract": "0x668e0c1f9d7b70a99dd9e06eadd4c784d641afbc", + "eventname": "Transfer", + "state": { + "type": "Array", + "value": [ + { + "type": "ByteString", + "value": "CqOHtT6Wt5iaYxQxoFbdH0CgQvY=" + }, + { + "type": "Any" + }, + { + "type": "Integer", + "value": "18083410" + } + ] + } + }, + { + "contract": "0x668e0c1f9d7b70a99dd9e06eadd4c784d641afbc", + "eventname": "Transfer", + "state": { + "type": "Array", + "value": [ + { + "type": "Any" + }, + { + "type": "ByteString", + "value": "z6LDQN4w1uEMToIZiPSxToNRPog=" + }, + { + "type": "Integer", + "value": "1252390" + } + ] + } + } + ] + }, + { + "trigger": "PostPersist", + "vmstate": "HALT", + "gasconsumed": "2031260", + "exception": null, + "stack": [], + "notifications": [ + { + "contract": "0x668e0c1f9d7b70a99dd9e06eadd4c784d641afbc", + "eventname": "Transfer", + "state": { + "type": "Array", + "value": [ + { + "type": "Any" + }, + { + "type": "ByteString", + "value": "z6LDQN4w1uEMToIZiPSxToNRPog=" + }, + { + "type": "Integer", + "value": "50000000" + } + ] + } + } + ] + } + ] + } + } + }, + { + "Name": "getapplicationlogasync_triggertype", + "Request": { + "jsonrpc": "2.0", + "method": "getapplicationlog", + "params": [ "0x6ea186fe714b8168ede3b78461db8945c06d867da649852352dbe7cbf1ba3724", "OnPersist" ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "blockhash": "0x6ea186fe714b8168ede3b78461db8945c06d867da649852352dbe7cbf1ba3724", + "executions": [ + { + "trigger": "OnPersist", + "vmstate": "HALT", + "gasconsumed": "2031260", + "exception": null, + "stack": [], + "notifications": [ + { + "contract": "0x668e0c1f9d7b70a99dd9e06eadd4c784d641afbc", + "eventname": "Transfer", + "state": { + "type": "Array", + "value": [ + { + "type": "ByteString", + "value": "CqOHtT6Wt5iaYxQxoFbdH0CgQvY=" + }, + { + "type": "Any" + }, + { + "type": "Integer", + "value": "18083410" + } + ] + } + }, + { + "contract": "0x668e0c1f9d7b70a99dd9e06eadd4c784d641afbc", + "eventname": "Transfer", + "state": { + "type": "Array", + "value": [ + { + "type": "Any" + }, + { + "type": "ByteString", + "value": "z6LDQN4w1uEMToIZiPSxToNRPog=" + }, + { + "type": "Integer", + "value": "1252390" + } + ] + } + } + ] + } + ] + } + } + }, + { + "Name": "getnep17transfersasync", + "Request": { + "jsonrpc": "2.0", + "method": "getnep17transfers", + "params": [ "NVVwFw6XyhtRCFQ8SpUTMdPyYt4Vd9A1XQ", 0, 1868595301000 ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "sent": [ + { + "timestamp": 1579250114541, + "assethash": "0x8c23f196d8a1bfd103a9dcb1f9ccf0c611377d3b", + "transferaddress": "NVVwFw6XyhtRCFQ8SpUTMdPyYt4Vd9A1XQ", + "amount": "1000000000", + "blockindex": 603, + "transfernotifyindex": 0, + "txhash": "0x5e177b8d1dc33e9103c0cfd42f6dbf4efbe43029e2d6a18ea5ba0cb8437056b3" + }, + { + "timestamp": 1579406581635, + "assethash": "0x8c23f196d8a1bfd103a9dcb1f9ccf0c611377d3b", + "transferaddress": "NUMK37TV9yYKbJr1Gufh74nZiM623eBLqX", + "amount": "1000000000", + "blockindex": 1525, + "transfernotifyindex": 0, + "txhash": "0xc9c618b48972b240e0058d97b8d79b807ad51015418c84012765298526aeb77d" + } + ], + "received": [ + { + "timestamp": 1579250114541, + "assethash": "0x8c23f196d8a1bfd103a9dcb1f9ccf0c611377d3b", + "transferaddress": "NVVwFw6XyhtRCFQ8SpUTMdPyYt4Vd9A1XQ", + "amount": "1000000000", + "blockindex": 603, + "transfernotifyindex": 0, + "txhash": "0x5e177b8d1dc33e9103c0cfd42f6dbf4efbe43029e2d6a18ea5ba0cb8437056b3" + } + ], + "address": "NVVwFw6XyhtRCFQ8SpUTMdPyYt4Vd9A1XQ" + } + } + }, + { + "Name": "getnep17transfersasync_with_null_transferaddress", + "Request": { + "jsonrpc": "2.0", + "method": "getnep17transfers", + "params": [ "Ncb7jVsYWBt1q5T5k3ZTP8bn5eK4DuanLd", 0, 1868595301000 ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "sent": [ + { + "timestamp": 1579250114541, + "assethash": "0x8c23f196d8a1bfd103a9dcb1f9ccf0c611377d3b", + "transferaddress": null, + "amount": "1000000000", + "blockindex": 603, + "transfernotifyindex": 0, + "txhash": "0x5e177b8d1dc33e9103c0cfd42f6dbf4efbe43029e2d6a18ea5ba0cb8437056b3" + }, + { + "timestamp": 1579406581635, + "assethash": "0x8c23f196d8a1bfd103a9dcb1f9ccf0c611377d3b", + "transferaddress": "Ncb7jVsYWBt1q5T5k3ZTP8bn5eK4DuanLd", + "amount": "1000000000", + "blockindex": 1525, + "transfernotifyindex": 0, + "txhash": "0xc9c618b48972b240e0058d97b8d79b807ad51015418c84012765298526aeb77d" + } + ], + "received": [ + { + "timestamp": 1579250114541, + "assethash": "0x8c23f196d8a1bfd103a9dcb1f9ccf0c611377d3b", + "transferaddress": null, + "amount": "1000000000", + "blockindex": 603, + "transfernotifyindex": 0, + "txhash": "0x5e177b8d1dc33e9103c0cfd42f6dbf4efbe43029e2d6a18ea5ba0cb8437056b3" + } + ], + "address": "Ncb7jVsYWBt1q5T5k3ZTP8bn5eK4DuanLd" + } + } + }, + { + "Name": "getnep17balancesasync", + "Request": { + "jsonrpc": "2.0", + "method": "getnep17balances", + "params": [ "NVVwFw6XyhtRCFQ8SpUTMdPyYt4Vd9A1XQ" ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "balance": [ + { + "assethash": "0x8c23f196d8a1bfd103a9dcb1f9ccf0c611377d3b", + "amount": "719978585420", + "lastupdatedblock": 3101 + }, + { + "assethash": "0x9bde8f209c88dd0e7ca3bf0af0f476cdd8207789", + "amount": "89999810", + "lastupdatedblock": 3096 + } + ], + "address": "NVVwFw6XyhtRCFQ8SpUTMdPyYt4Vd9A1XQ" + } + } + } +] diff --git a/tests/Neo.Network.RPC.Tests/TestUtils.cs b/tests/Neo.Network.RPC.Tests/TestUtils.cs new file mode 100644 index 0000000000..068de3388b --- /dev/null +++ b/tests/Neo.Network.RPC.Tests/TestUtils.cs @@ -0,0 +1,95 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TestUtils.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using Neo.Network.P2P.Payloads; +using Neo.Network.RPC.Models; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Neo.Network.RPC.Tests +{ + internal static class TestUtils + { + public readonly static List RpcTestCases = ((JArray)JToken.Parse(File.ReadAllText("RpcTestCases.json"))).Select(p => RpcTestCase.FromJson((JObject)p)).ToList(); + + public static Block GetBlock(int txCount) + { + return new Block + { + Header = new Header + { + PrevHash = UInt256.Zero, + MerkleRoot = UInt256.Zero, + NextConsensus = UInt160.Zero, + Witness = new Witness + { + InvocationScript = new byte[0], + VerificationScript = new byte[0] + } + }, + Transactions = Enumerable.Range(0, txCount).Select(p => GetTransaction()).ToArray() + }; + } + + public static Header GetHeader() + { + return GetBlock(0).Header; + } + + public static Transaction GetTransaction() + { + return new Transaction + { + Script = new byte[1], + Signers = new Signer[] { new Signer { Account = UInt160.Zero } }, + Attributes = new TransactionAttribute[0], + Witnesses = new Witness[] + { + new Witness + { + InvocationScript = new byte[0], + VerificationScript = new byte[0] + } + } + }; + } + } + + internal class RpcTestCase + { + public string Name { get; set; } + public RpcRequest Request { get; set; } + public RpcResponse Response { get; set; } + + public JObject ToJson() + { + return new JObject + { + ["Name"] = Name, + ["Request"] = Request.ToJson(), + ["Response"] = Response.ToJson(), + }; + } + + public static RpcTestCase FromJson(JObject json) + { + return new RpcTestCase + { + Name = json["Name"].AsString(), + Request = RpcRequest.FromJson((JObject)json["Request"]), + Response = RpcResponse.FromJson((JObject)json["Response"]), + }; + } + + } +} diff --git a/tests/Neo.Network.RPC.Tests/UT_ContractClient.cs b/tests/Neo.Network.RPC.Tests/UT_ContractClient.cs new file mode 100644 index 0000000000..c3cf226a3e --- /dev/null +++ b/tests/Neo.Network.RPC.Tests/UT_ContractClient.cs @@ -0,0 +1,81 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ContractClient.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Neo.SmartContract; +using Neo.SmartContract.Manifest; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using System.Threading.Tasks; + +namespace Neo.Network.RPC.Tests +{ + [TestClass] + public class UT_ContractClient + { + Mock rpcClientMock; + KeyPair keyPair1; + UInt160 sender; + + [TestInitialize] + public void TestSetup() + { + keyPair1 = new KeyPair(Wallet.GetPrivateKeyFromWIF("KyXwTh1hB76RRMquSvnxZrJzQx7h9nQP2PCRL38v6VDb5ip3nf1p")); + sender = Contract.CreateSignatureRedeemScript(keyPair1.PublicKey).ToScriptHash(); + rpcClientMock = UT_TransactionManager.MockRpcClient(sender, new byte[0]); + } + + [TestMethod] + public async Task TestInvoke() + { + byte[] testScript = NativeContract.GAS.Hash.MakeScript("balanceOf", UInt160.Zero); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.ByteArray, Value = "00e057eb481b".HexToBytes() }); + + ContractClient contractClient = new ContractClient(rpcClientMock.Object); + var result = await contractClient.TestInvokeAsync(NativeContract.GAS.Hash, "balanceOf", UInt160.Zero); + + Assert.AreEqual(30000000000000L, (long)result.Stack[0].GetInteger()); + } + + [TestMethod] + public async Task TestDeployContract() + { + byte[] script; + var manifest = new ContractManifest() + { + Permissions = new[] { ContractPermission.DefaultPermission }, + Abi = new ContractAbi() + { + Events = new ContractEventDescriptor[0], + Methods = new ContractMethodDescriptor[0] + }, + Groups = new ContractGroup[0], + Trusts = WildcardContainer.Create(), + SupportedStandards = new string[] { "NEP-10" }, + Extra = null, + }; + using (ScriptBuilder sb = new ScriptBuilder()) + { + sb.EmitDynamicCall(NativeContract.ContractManagement.Hash, "deploy", new byte[1], manifest.ToJson().ToString()); + script = sb.ToArray(); + } + + UT_TransactionManager.MockInvokeScript(rpcClientMock, script, new ContractParameter()); + + ContractClient contractClient = new ContractClient(rpcClientMock.Object); + var result = await contractClient.CreateDeployContractTxAsync(new byte[1], manifest, keyPair1); + + Assert.IsNotNull(result); + } + } +} diff --git a/tests/Neo.Network.RPC.Tests/UT_Nep17API.cs b/tests/Neo.Network.RPC.Tests/UT_Nep17API.cs new file mode 100644 index 0000000000..3bfb4e87ff --- /dev/null +++ b/tests/Neo.Network.RPC.Tests/UT_Nep17API.cs @@ -0,0 +1,168 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Nep17API.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Neo.Json; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using System.Linq; +using System.Numerics; +using System.Threading.Tasks; +using static Neo.Helper; + +namespace Neo.Network.RPC.Tests +{ + [TestClass] + public class UT_Nep17API + { + Mock rpcClientMock; + KeyPair keyPair1; + UInt160 sender; + Nep17API nep17API; + + [TestInitialize] + public void TestSetup() + { + keyPair1 = new KeyPair(Wallet.GetPrivateKeyFromWIF("KyXwTh1hB76RRMquSvnxZrJzQx7h9nQP2PCRL38v6VDb5ip3nf1p")); + sender = Contract.CreateSignatureRedeemScript(keyPair1.PublicKey).ToScriptHash(); + rpcClientMock = UT_TransactionManager.MockRpcClient(sender, new byte[0]); + nep17API = new Nep17API(rpcClientMock.Object); + } + + [TestMethod] + public async Task TestBalanceOf() + { + byte[] testScript = NativeContract.GAS.Hash.MakeScript("balanceOf", UInt160.Zero); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(10000) }); + + var balance = await nep17API.BalanceOfAsync(NativeContract.GAS.Hash, UInt160.Zero); + Assert.AreEqual(10000, (int)balance); + } + + [TestMethod] + public async Task TestGetSymbol() + { + byte[] testScript = NativeContract.GAS.Hash.MakeScript("symbol"); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.String, Value = NativeContract.GAS.Symbol }); + + var result = await nep17API.SymbolAsync(NativeContract.GAS.Hash); + Assert.AreEqual(NativeContract.GAS.Symbol, result); + } + + [TestMethod] + public async Task TestGetDecimals() + { + byte[] testScript = NativeContract.GAS.Hash.MakeScript("decimals"); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(NativeContract.GAS.Decimals) }); + + var result = await nep17API.DecimalsAsync(NativeContract.GAS.Hash); + Assert.AreEqual(NativeContract.GAS.Decimals, result); + } + + [TestMethod] + public async Task TestGetTotalSupply() + { + byte[] testScript = NativeContract.GAS.Hash.MakeScript("totalSupply"); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1_00000000) }); + + var result = await nep17API.TotalSupplyAsync(NativeContract.GAS.Hash); + Assert.AreEqual(1_00000000, (int)result); + } + + [TestMethod] + public async Task TestGetTokenInfo() + { + UInt160 scriptHash = NativeContract.GAS.Hash; + byte[] testScript = Concat( + scriptHash.MakeScript("symbol"), + scriptHash.MakeScript("decimals"), + scriptHash.MakeScript("totalSupply")); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, + new ContractParameter { Type = ContractParameterType.String, Value = NativeContract.GAS.Symbol }, + new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(NativeContract.GAS.Decimals) }, + new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1_00000000) }); + + scriptHash = NativeContract.NEO.Hash; + testScript = Concat( + scriptHash.MakeScript("symbol"), + scriptHash.MakeScript("decimals"), + scriptHash.MakeScript("totalSupply")); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, + new ContractParameter { Type = ContractParameterType.String, Value = NativeContract.NEO.Symbol }, + new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(NativeContract.NEO.Decimals) }, + new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1_00000000) }); + + var tests = TestUtils.RpcTestCases.Where(p => p.Name == "getcontractstateasync"); + var haveGasTokenUT = false; + var haveNeoTokenUT = false; + foreach (var test in tests) + { + rpcClientMock.Setup(p => p.RpcSendAsync("getcontractstate", It.Is(u => true))) + .ReturnsAsync(test.Response.Result) + .Verifiable(); + if (test.Request.Params[0].AsString() == NativeContract.GAS.Hash.ToString() || test.Request.Params[0].AsString().Equals(NativeContract.GAS.Name, System.StringComparison.OrdinalIgnoreCase)) + { + var result = await nep17API.GetTokenInfoAsync(NativeContract.GAS.Name.ToLower()); + Assert.AreEqual(NativeContract.GAS.Symbol, result.Symbol); + Assert.AreEqual(8, result.Decimals); + Assert.AreEqual(1_00000000, (int)result.TotalSupply); + Assert.AreEqual("GasToken", result.Name); + + result = await nep17API.GetTokenInfoAsync(NativeContract.GAS.Hash); + Assert.AreEqual(NativeContract.GAS.Symbol, result.Symbol); + Assert.AreEqual(8, result.Decimals); + Assert.AreEqual(1_00000000, (int)result.TotalSupply); + Assert.AreEqual("GasToken", result.Name); + haveGasTokenUT = true; + } + else if (test.Request.Params[0].AsString() == NativeContract.NEO.Hash.ToString() || test.Request.Params[0].AsString().Equals(NativeContract.NEO.Name, System.StringComparison.OrdinalIgnoreCase)) + { + var result = await nep17API.GetTokenInfoAsync(NativeContract.NEO.Name.ToLower()); + Assert.AreEqual(NativeContract.NEO.Symbol, result.Symbol); + Assert.AreEqual(0, result.Decimals); + Assert.AreEqual(1_00000000, (int)result.TotalSupply); + Assert.AreEqual("NeoToken", result.Name); + + result = await nep17API.GetTokenInfoAsync(NativeContract.NEO.Hash); + Assert.AreEqual(NativeContract.NEO.Symbol, result.Symbol); + Assert.AreEqual(0, result.Decimals); + Assert.AreEqual(1_00000000, (int)result.TotalSupply); + Assert.AreEqual("NeoToken", result.Name); + haveNeoTokenUT = true; + } + } + Assert.IsTrue(haveGasTokenUT && haveNeoTokenUT); //Update RpcTestCases.json + } + + [TestMethod] + public async Task TestTransfer() + { + byte[] testScript = NativeContract.GAS.Hash.MakeScript("transfer", sender, UInt160.Zero, new BigInteger(1_00000000), null) + .Concat(new[] { (byte)OpCode.ASSERT }) + .ToArray(); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter()); + + var client = rpcClientMock.Object; + var result = await nep17API.CreateTransferTxAsync(NativeContract.GAS.Hash, keyPair1, UInt160.Zero, new BigInteger(1_00000000), null, true); + + testScript = NativeContract.GAS.Hash.MakeScript("transfer", sender, UInt160.Zero, new BigInteger(1_00000000), string.Empty) + .Concat(new[] { (byte)OpCode.ASSERT }) + .ToArray(); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter()); + + result = await nep17API.CreateTransferTxAsync(NativeContract.GAS.Hash, keyPair1, UInt160.Zero, new BigInteger(1_00000000), string.Empty, true); + Assert.IsNotNull(result); + } + } +} diff --git a/tests/Neo.Network.RPC.Tests/UT_PolicyAPI.cs b/tests/Neo.Network.RPC.Tests/UT_PolicyAPI.cs new file mode 100644 index 0000000000..7defa163ad --- /dev/null +++ b/tests/Neo.Network.RPC.Tests/UT_PolicyAPI.cs @@ -0,0 +1,79 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_PolicyAPI.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using System.Numerics; +using System.Threading.Tasks; + +namespace Neo.Network.RPC.Tests +{ + [TestClass] + public class UT_PolicyAPI + { + Mock rpcClientMock; + KeyPair keyPair1; + UInt160 sender; + PolicyAPI policyAPI; + + [TestInitialize] + public void TestSetup() + { + keyPair1 = new KeyPair(Wallet.GetPrivateKeyFromWIF("KyXwTh1hB76RRMquSvnxZrJzQx7h9nQP2PCRL38v6VDb5ip3nf1p")); + sender = Contract.CreateSignatureRedeemScript(keyPair1.PublicKey).ToScriptHash(); + rpcClientMock = UT_TransactionManager.MockRpcClient(sender, new byte[0]); + policyAPI = new PolicyAPI(rpcClientMock.Object); + } + + [TestMethod] + public async Task TestGetExecFeeFactor() + { + byte[] testScript = NativeContract.Policy.Hash.MakeScript("getExecFeeFactor"); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(30) }); + + var result = await policyAPI.GetExecFeeFactorAsync(); + Assert.AreEqual(30u, result); + } + + [TestMethod] + public async Task TestGetStoragePrice() + { + byte[] testScript = NativeContract.Policy.Hash.MakeScript("getStoragePrice"); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(100000) }); + + var result = await policyAPI.GetStoragePriceAsync(); + Assert.AreEqual(100000u, result); + } + + [TestMethod] + public async Task TestGetFeePerByte() + { + byte[] testScript = NativeContract.Policy.Hash.MakeScript("getFeePerByte"); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1000) }); + + var result = await policyAPI.GetFeePerByteAsync(); + Assert.AreEqual(1000L, result); + } + + [TestMethod] + public async Task TestIsBlocked() + { + byte[] testScript = NativeContract.Policy.Hash.MakeScript("isBlocked", UInt160.Zero); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Boolean, Value = true }); + var result = await policyAPI.IsBlockedAsync(UInt160.Zero); + Assert.AreEqual(true, result); + } + } +} diff --git a/tests/Neo.Network.RPC.Tests/UT_RpcClient.cs b/tests/Neo.Network.RPC.Tests/UT_RpcClient.cs new file mode 100644 index 0000000000..4af0f557e3 --- /dev/null +++ b/tests/Neo.Network.RPC.Tests/UT_RpcClient.cs @@ -0,0 +1,525 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_RpcClient.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Moq.Protected; +using Neo.IO; +using Neo.Json; +using Neo.Network.P2P.Payloads; +using Neo.Network.RPC.Models; +using Neo.SmartContract; +using System; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Neo.Network.RPC.Tests +{ + [TestClass] + public class UT_RpcClient + { + RpcClient rpc; + Mock handlerMock; + + [TestInitialize] + public void TestSetup() + { + handlerMock = new Mock(MockBehavior.Strict); + + // use real http client with mocked handler here + var httpClient = new HttpClient(handlerMock.Object); + rpc = new RpcClient(httpClient, new Uri("http://seed1.neo.org:10331"), null); + foreach (var test in TestUtils.RpcTestCases) + { + MockResponse(test.Request, test.Response); + } + } + + private void MockResponse(RpcRequest request, RpcResponse response) + { + handlerMock.Protected() + // Setup the PROTECTED method to mock + .Setup>( + "SendAsync", + ItExpr.Is(p => p.Content.ReadAsStringAsync().Result == request.ToJson().ToString()), + ItExpr.IsAny() + ) + // prepare the expected response of the mocked http call + .ReturnsAsync(new HttpResponseMessage() + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(response.ToJson().ToString()), + }) + .Verifiable(); + } + + [TestMethod] + public async Task TestErrorResponse() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == (nameof(rpc.SendRawTransactionAsync) + "error").ToLower()); + try + { + var result = await rpc.SendRawTransactionAsync(Convert.FromBase64String(test.Request.Params[0].AsString()).AsSerializable()); + } + catch (RpcException ex) + { + Assert.AreEqual(-500, ex.HResult); + Assert.AreEqual("InsufficientFunds", ex.Message); + } + } + + [TestMethod] + public async Task TestNoThrowErrorResponse() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == (nameof(rpc.SendRawTransactionAsync) + "error").ToLower()); + handlerMock = new Mock(MockBehavior.Strict); + handlerMock.Protected() + // Setup the PROTECTED method to mock + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + // prepare the expected response of the mocked http call + .ReturnsAsync(new HttpResponseMessage() + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(test.Response.ToJson().ToString()), + }) + .Verifiable(); + + var httpClient = new HttpClient(handlerMock.Object); + var client = new RpcClient(httpClient, new Uri("http://seed1.neo.org:10331"), null); + var response = await client.SendAsync(test.Request, false); + + Assert.IsNull(response.Result); + Assert.IsNotNull(response.Error); + Assert.AreEqual(-500, response.Error.Code); + Assert.AreEqual("InsufficientFunds", response.Error.Message); + } + + [TestMethod] + public void TestConstructorByUrlAndDispose() + { + //dummy url for test + var client = new RpcClient(new Uri("http://www.xxx.yyy")); + Action action = () => client.Dispose(); + action.Should().NotThrow(); + } + + [TestMethod] + public void TestConstructorWithBasicAuth() + { + var client = new RpcClient(new Uri("http://www.xxx.yyy"), "krain", "123456"); + client.Dispose(); + } + + #region Blockchain + + [TestMethod] + public async Task TestGetBestBlockHash() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.GetBestBlockHashAsync).ToLower()); + var result = await rpc.GetBestBlockHashAsync(); + Assert.AreEqual(test.Response.Result.AsString(), result); + } + + [TestMethod] + public async Task TestGetBlockHex() + { + var tests = TestUtils.RpcTestCases.Where(p => p.Name == nameof(rpc.GetBlockHexAsync).ToLower()); + foreach (var test in tests) + { + var result = await rpc.GetBlockHexAsync(test.Request.Params[0].AsString()); + Assert.AreEqual(test.Response.Result.AsString(), result); + } + } + + [TestMethod] + public async Task TestGetBlock() + { + var tests = TestUtils.RpcTestCases.Where(p => p.Name == nameof(rpc.GetBlockAsync).ToLower()); + foreach (var test in tests) + { + var result = await rpc.GetBlockAsync(test.Request.Params[0].AsString()); + Assert.AreEqual(test.Response.Result.AsString(), result.ToJson(rpc.protocolSettings).ToString()); + } + } + + [TestMethod] + public async Task TestGetBlockHeaderCount() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.GetBlockHeaderCountAsync).ToLower()); + var result = await rpc.GetBlockHeaderCountAsync(); + Assert.AreEqual(test.Response.Result.AsString(), result.ToString()); + } + + [TestMethod] + public async Task TestGetBlockCount() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.GetBlockCountAsync).ToLower()); + var result = await rpc.GetBlockCountAsync(); + Assert.AreEqual(test.Response.Result.AsString(), result.ToString()); + } + + [TestMethod] + public async Task TestGetBlockHash() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.GetBlockHashAsync).ToLower()); + var result = await rpc.GetBlockHashAsync((uint)test.Request.Params[0].AsNumber()); + Assert.AreEqual(test.Response.Result.AsString(), result.ToString()); + } + + [TestMethod] + public async Task TestGetBlockHeaderHex() + { + var tests = TestUtils.RpcTestCases.Where(p => p.Name == nameof(rpc.GetBlockHeaderHexAsync).ToLower()); + foreach (var test in tests) + { + var result = await rpc.GetBlockHeaderHexAsync(test.Request.Params[0].AsString()); + Assert.AreEqual(test.Response.Result.AsString(), result); + } + } + + [TestMethod] + public async Task TestGetBlockHeader() + { + var tests = TestUtils.RpcTestCases.Where(p => p.Name == nameof(rpc.GetBlockHeaderAsync).ToLower()); + foreach (var test in tests) + { + var result = await rpc.GetBlockHeaderAsync(test.Request.Params[0].AsString()); + Assert.AreEqual(test.Response.Result.ToString(), result.ToJson(rpc.protocolSettings).ToString()); + } + } + + [TestMethod] + public async Task TestGetCommittee() + { + var tests = TestUtils.RpcTestCases.Where(p => p.Name == nameof(rpc.GetCommitteeAsync).ToLower()); + foreach (var test in tests) + { + var result = await rpc.GetCommitteeAsync(); + Assert.AreEqual(test.Response.Result.ToString(), ((JArray)result.Select(p => (JToken)p).ToArray()).ToString()); + } + } + + [TestMethod] + public async Task TestGetContractState() + { + var tests = TestUtils.RpcTestCases.Where(p => p.Name == nameof(rpc.GetContractStateAsync).ToLower()); + foreach (var test in tests) + { + var result = await rpc.GetContractStateAsync(test.Request.Params[0].AsString()); + Assert.AreEqual(test.Response.Result.ToString(), result.ToJson().ToString()); + } + } + + [TestMethod] + public async Task TestGetNativeContracts() + { + var tests = TestUtils.RpcTestCases.Where(p => p.Name == nameof(rpc.GetNativeContractsAsync).ToLower()); + foreach (var test in tests) + { + var result = await rpc.GetNativeContractsAsync(); + Assert.AreEqual(test.Response.Result.ToString(), ((JArray)result.Select(p => p.ToJson()).ToArray()).ToString()); + } + } + + [TestMethod] + public async Task TestGetRawMempool() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.GetRawMempoolAsync).ToLower()); + var result = await rpc.GetRawMempoolAsync(); + Assert.AreEqual(test.Response.Result.ToString(), ((JArray)result.Select(p => (JToken)p).ToArray()).ToString()); + } + + [TestMethod] + public async Task TestGetRawMempoolBoth() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.GetRawMempoolBothAsync).ToLower()); + var result = await rpc.GetRawMempoolBothAsync(); + Assert.AreEqual(test.Response.Result.ToString(), result.ToJson().ToString()); + } + + [TestMethod] + public async Task TestGetRawTransactionHex() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.GetRawTransactionHexAsync).ToLower()); + var result = await rpc.GetRawTransactionHexAsync(test.Request.Params[0].AsString()); + Assert.AreEqual(test.Response.Result.AsString(), result); + } + + [TestMethod] + public async Task TestGetRawTransaction() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.GetRawTransactionAsync).ToLower()); + var result = await rpc.GetRawTransactionAsync(test.Request.Params[0].AsString()); + Assert.AreEqual(test.Response.Result.ToString(), result.ToJson(rpc.protocolSettings).ToString()); + } + + [TestMethod] + public async Task TestGetStorage() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.GetStorageAsync).ToLower()); + var result = await rpc.GetStorageAsync(test.Request.Params[0].AsString(), test.Request.Params[1].AsString()); + Assert.AreEqual(test.Response.Result.AsString(), result); + } + + [TestMethod] + public async Task TestGetTransactionHeight() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.GetTransactionHeightAsync).ToLower()); + var result = await rpc.GetTransactionHeightAsync(test.Request.Params[0].AsString()); + Assert.AreEqual(test.Response.Result.ToString(), result.ToString()); + } + + [TestMethod] + public async Task TestGetNextBlockValidators() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.GetNextBlockValidatorsAsync).ToLower()); + var result = await rpc.GetNextBlockValidatorsAsync(); + Assert.AreEqual(test.Response.Result.ToString(), ((JArray)result.Select(p => p.ToJson()).ToArray()).ToString()); + } + + #endregion Blockchain + + #region Node + + [TestMethod] + public async Task TestGetConnectionCount() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.GetConnectionCountAsync).ToLower()); + var result = await rpc.GetConnectionCountAsync(); + Assert.AreEqual(test.Response.Result.ToString(), result.ToString()); + } + + [TestMethod] + public async Task TestGetPeers() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.GetPeersAsync).ToLower()); + var result = await rpc.GetPeersAsync(); + Assert.AreEqual(test.Response.Result.ToString(), result.ToJson().ToString()); + } + + [TestMethod] + public async Task TestGetVersion() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.GetVersionAsync).ToLower()); + var result = await rpc.GetVersionAsync(); + Assert.AreEqual(test.Response.Result.ToString(), result.ToJson().ToString()); + } + + [TestMethod] + public async Task TestSendRawTransaction() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.SendRawTransactionAsync).ToLower()); + var result = await rpc.SendRawTransactionAsync(Convert.FromBase64String(test.Request.Params[0].AsString()).AsSerializable()); + Assert.AreEqual(test.Response.Result["hash"].AsString(), result.ToString()); + } + + [TestMethod] + public async Task TestSubmitBlock() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.SubmitBlockAsync).ToLower()); + var result = await rpc.SubmitBlockAsync(Convert.FromBase64String(test.Request.Params[0].AsString())); + Assert.AreEqual(test.Response.Result["hash"].AsString(), result.ToString()); + } + + #endregion Node + + #region SmartContract + + [TestMethod] + public async Task TestInvokeFunction() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.InvokeFunctionAsync).ToLower()); + var result = await rpc.InvokeFunctionAsync(test.Request.Params[0].AsString(), test.Request.Params[1].AsString(), + ((JArray)test.Request.Params[2]).Select(p => RpcStack.FromJson((JObject)p)).ToArray()); + Assert.AreEqual(test.Response.Result.ToString(), result.ToJson().ToString()); + + // TODO test verify method + } + + [TestMethod] + public async Task TestInvokeScript() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.InvokeScriptAsync).ToLower()); + var result = await rpc.InvokeScriptAsync(Convert.FromBase64String(test.Request.Params[0].AsString())); + Assert.AreEqual(test.Response.Result.ToString(), result.ToJson().ToString()); + } + + [TestMethod] + public async Task TestGetUnclaimedGas() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.GetUnclaimedGasAsync).ToLower()); + var result = await rpc.GetUnclaimedGasAsync(test.Request.Params[0].AsString()); + Assert.AreEqual(result.ToJson().AsString(), RpcUnclaimedGas.FromJson(result.ToJson()).ToJson().AsString()); + Assert.AreEqual(test.Response.Result["unclaimed"].AsString(), result.Unclaimed.ToString()); + } + + #endregion SmartContract + + #region Utilities + + [TestMethod] + public async Task TestListPlugins() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.ListPluginsAsync).ToLower()); + var result = await rpc.ListPluginsAsync(); + Assert.AreEqual(test.Response.Result.ToString(), ((JArray)result.Select(p => p.ToJson()).ToArray()).ToString()); + } + + [TestMethod] + public async Task TestValidateAddress() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.ValidateAddressAsync).ToLower()); + var result = await rpc.ValidateAddressAsync(test.Request.Params[0].AsString()); + Assert.AreEqual(test.Response.Result.ToString(), result.ToJson().ToString()); + } + + #endregion Utilities + + #region Wallet + + [TestMethod] + public async Task TestCloseWallet() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.CloseWalletAsync).ToLower()); + var result = await rpc.CloseWalletAsync(); + Assert.AreEqual(test.Response.Result.AsBoolean(), result); + } + + [TestMethod] + public async Task TestDumpPrivKey() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.DumpPrivKeyAsync).ToLower()); + var result = await rpc.DumpPrivKeyAsync(test.Request.Params[0].AsString()); + Assert.AreEqual(test.Response.Result.AsString(), result); + } + + [TestMethod] + public async Task TestGetNewAddress() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.GetNewAddressAsync).ToLower()); + var result = await rpc.GetNewAddressAsync(); + Assert.AreEqual(test.Response.Result.AsString(), result); + } + + [TestMethod] + public async Task TestGetWalletBalance() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.GetWalletBalanceAsync).ToLower()); + var result = await rpc.GetWalletBalanceAsync(test.Request.Params[0].AsString()); + Assert.AreEqual(test.Response.Result["balance"].AsString(), result.Value.ToString()); + } + + [TestMethod] + public async Task TestGetWalletUnclaimedGas() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.GetWalletUnclaimedGasAsync).ToLower()); + var result = await rpc.GetWalletUnclaimedGasAsync(); + Assert.AreEqual(test.Response.Result.AsString(), result.ToString()); + } + + [TestMethod] + public async Task TestImportPrivKey() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.ImportPrivKeyAsync).ToLower()); + var result = await rpc.ImportPrivKeyAsync(test.Request.Params[0].AsString()); + Assert.AreEqual(test.Response.Result.ToString(), result.ToJson().ToString()); + } + + [TestMethod] + public async Task TestListAddress() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.ListAddressAsync).ToLower()); + var result = await rpc.ListAddressAsync(); + Assert.AreEqual(test.Response.Result.ToString(), ((JArray)result.Select(p => p.ToJson()).ToArray()).ToString()); + } + + [TestMethod] + public async Task TestOpenWallet() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.OpenWalletAsync).ToLower()); + var result = await rpc.OpenWalletAsync(test.Request.Params[0].AsString(), test.Request.Params[1].AsString()); + Assert.AreEqual(test.Response.Result.AsBoolean(), result); + } + + [TestMethod] + public async Task TestSendFrom() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.SendFromAsync).ToLower()); + var result = await rpc.SendFromAsync(test.Request.Params[0].AsString(), test.Request.Params[1].AsString(), + test.Request.Params[2].AsString(), test.Request.Params[3].AsString()); + Assert.AreEqual(test.Response.Result.ToString(), result.ToString()); + } + + [TestMethod] + public async Task TestSendMany() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.SendManyAsync).ToLower()); + var result = await rpc.SendManyAsync(test.Request.Params[0].AsString(), ((JArray)test.Request.Params[1]).Select(p => RpcTransferOut.FromJson((JObject)p, rpc.protocolSettings))); + Assert.AreEqual(test.Response.Result.ToString(), result.ToString()); + } + + [TestMethod] + public async Task TestSendToAddress() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.SendToAddressAsync).ToLower()); + var result = await rpc.SendToAddressAsync(test.Request.Params[0].AsString(), test.Request.Params[1].AsString(), test.Request.Params[2].AsString()); + Assert.AreEqual(test.Response.Result.ToString(), result.ToString()); + } + + #endregion Wallet + + #region Plugins + + [TestMethod()] + public async Task GetApplicationLogTest() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.GetApplicationLogAsync).ToLower()); + var result = await rpc.GetApplicationLogAsync(test.Request.Params[0].AsString()); + Assert.AreEqual(test.Response.Result.ToString(), result.ToJson().ToString()); + } + + [TestMethod()] + public async Task GetApplicationLogTest_TriggerType() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == (nameof(rpc.GetApplicationLogAsync) + "_triggertype").ToLower()); + var result = await rpc.GetApplicationLogAsync(test.Request.Params[0].AsString(), TriggerType.OnPersist); + Assert.AreEqual(test.Response.Result.ToString(), result.ToJson().ToString()); + } + + [TestMethod()] + public async Task GetNep17TransfersTest() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.GetNep17TransfersAsync).ToLower()); + var result = await rpc.GetNep17TransfersAsync(test.Request.Params[0].AsString(), (ulong)test.Request.Params[1].AsNumber(), (ulong)test.Request.Params[2].AsNumber()); + Assert.AreEqual(test.Response.Result.ToString(), result.ToJson(rpc.protocolSettings).ToString()); + test = TestUtils.RpcTestCases.Find(p => p.Name == (nameof(rpc.GetNep17TransfersAsync).ToLower() + "_with_null_transferaddress")); + result = await rpc.GetNep17TransfersAsync(test.Request.Params[0].AsString(), (ulong)test.Request.Params[1].AsNumber(), (ulong)test.Request.Params[2].AsNumber()); + Assert.AreEqual(test.Response.Result.ToString(), result.ToJson(rpc.protocolSettings).ToString()); + } + + [TestMethod()] + public async Task GetNep17BalancesTest() + { + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.GetNep17BalancesAsync).ToLower()); + var result = await rpc.GetNep17BalancesAsync(test.Request.Params[0].AsString()); + Assert.AreEqual(test.Response.Result.ToString(), result.ToJson(rpc.protocolSettings).ToString()); + } + + #endregion Plugins + } +} diff --git a/tests/Neo.Network.RPC.Tests/UT_RpcModels.cs b/tests/Neo.Network.RPC.Tests/UT_RpcModels.cs new file mode 100644 index 0000000000..43eb7cfe7b --- /dev/null +++ b/tests/Neo.Network.RPC.Tests/UT_RpcModels.cs @@ -0,0 +1,175 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_RpcModels.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Neo.Json; +using Neo.Network.RPC.Models; +using System; +using System.Linq; +using System.Net.Http; + +namespace Neo.Network.RPC.Tests +{ + [TestClass()] + public class UT_RpcModels + { + RpcClient rpc; + Mock handlerMock; + + [TestInitialize] + public void TestSetup() + { + handlerMock = new Mock(MockBehavior.Strict); + + // use real http client with mocked handler here + var httpClient = new HttpClient(handlerMock.Object); + rpc = new RpcClient(httpClient, new Uri("http://seed1.neo.org:10331"), null); + } + + [TestMethod()] + public void TestRpcAccount() + { + JToken json = TestUtils.RpcTestCases.Find(p => p.Name == nameof(RpcClient.ImportPrivKeyAsync).ToLower()).Response.Result; + var item = RpcAccount.FromJson((JObject)json); + Assert.AreEqual(json.ToString(), item.ToJson().ToString()); + } + + [TestMethod()] + public void TestRpcApplicationLog() + { + JToken json = TestUtils.RpcTestCases.Find(p => p.Name == nameof(RpcClient.GetApplicationLogAsync).ToLower()).Response.Result; + var item = RpcApplicationLog.FromJson((JObject)json, rpc.protocolSettings); + Assert.AreEqual(json.ToString(), item.ToJson().ToString()); + } + + [TestMethod()] + public void TestRpcBlock() + { + JToken json = TestUtils.RpcTestCases.Find(p => p.Name == nameof(RpcClient.GetBlockAsync).ToLower()).Response.Result; + var item = RpcBlock.FromJson((JObject)json, rpc.protocolSettings); + Assert.AreEqual(json.ToString(), item.ToJson(rpc.protocolSettings).ToString()); + } + + [TestMethod()] + public void TestRpcBlockHeader() + { + JToken json = TestUtils.RpcTestCases.Find(p => p.Name == nameof(RpcClient.GetBlockHeaderAsync).ToLower()).Response.Result; + var item = RpcBlockHeader.FromJson((JObject)json, rpc.protocolSettings); + Assert.AreEqual(json.ToString(), item.ToJson(rpc.protocolSettings).ToString()); + } + + [TestMethod()] + public void TestGetContractState() + { + JToken json = TestUtils.RpcTestCases.Find(p => p.Name == nameof(RpcClient.GetContractStateAsync).ToLower()).Response.Result; + var item = RpcContractState.FromJson((JObject)json); + Assert.AreEqual(json.ToString(), item.ToJson().ToString()); + + var nef = RpcNefFile.FromJson((JObject)json["nef"]); + Assert.AreEqual(json["nef"].ToString(), nef.ToJson().ToString()); + } + + [TestMethod()] + public void TestRpcInvokeResult() + { + JToken json = TestUtils.RpcTestCases.Find(p => p.Name == nameof(RpcClient.InvokeFunctionAsync).ToLower()).Response.Result; + var item = RpcInvokeResult.FromJson((JObject)json); + Assert.AreEqual(json.ToString(), item.ToJson().ToString()); + } + + [TestMethod()] + public void TestRpcMethodToken() + { + RpcMethodToken.FromJson((JObject)JToken.Parse("{\"hash\": \"0x0e1b9bfaa44e60311f6f3c96cfcd6d12c2fc3add\", \"method\":\"test\",\"paramcount\":\"1\",\"hasreturnvalue\":\"true\",\"callflags\":\"All\"}")); + } + + [TestMethod()] + public void TestRpcNep17Balances() + { + JToken json = TestUtils.RpcTestCases.Find(p => p.Name == nameof(RpcClient.GetNep17BalancesAsync).ToLower()).Response.Result; + var item = RpcNep17Balances.FromJson((JObject)json, rpc.protocolSettings); + Assert.AreEqual(json.ToString(), item.ToJson(rpc.protocolSettings).ToString()); + } + + [TestMethod()] + public void TestRpcNep17Transfers() + { + JToken json = TestUtils.RpcTestCases.Find(p => p.Name == nameof(RpcClient.GetNep17TransfersAsync).ToLower()).Response.Result; + var item = RpcNep17Transfers.FromJson((JObject)json, rpc.protocolSettings); + Assert.AreEqual(json.ToString(), item.ToJson(rpc.protocolSettings).ToString()); + } + + [TestMethod()] + public void TestRpcPeers() + { + JToken json = TestUtils.RpcTestCases.Find(p => p.Name == nameof(RpcClient.GetPeersAsync).ToLower()).Response.Result; + var item = RpcPeers.FromJson((JObject)json); + Assert.AreEqual(json.ToString(), item.ToJson().ToString()); + } + + [TestMethod()] + public void TestRpcPlugin() + { + JToken json = TestUtils.RpcTestCases.Find(p => p.Name == nameof(RpcClient.ListPluginsAsync).ToLower()).Response.Result; + var item = ((JArray)json).Select(p => RpcPlugin.FromJson((JObject)p)); + Assert.AreEqual(json.ToString(), ((JArray)item.Select(p => p.ToJson()).ToArray()).ToString()); + } + + [TestMethod()] + public void TestRpcRawMemPool() + { + JToken json = TestUtils.RpcTestCases.Find(p => p.Name == nameof(RpcClient.GetRawMempoolBothAsync).ToLower()).Response.Result; + var item = RpcRawMemPool.FromJson((JObject)json); + Assert.AreEqual(json.ToString(), item.ToJson().ToString()); + } + + [TestMethod()] + public void TestRpcTransaction() + { + JToken json = TestUtils.RpcTestCases.Find(p => p.Name == nameof(RpcClient.GetRawTransactionAsync).ToLower()).Response.Result; + var item = RpcTransaction.FromJson((JObject)json, rpc.protocolSettings); + Assert.AreEqual(json.ToString(), item.ToJson(rpc.protocolSettings).ToString()); + } + + [TestMethod()] + public void TestRpcTransferOut() + { + JToken json = TestUtils.RpcTestCases.Find(p => p.Name == nameof(RpcClient.SendManyAsync).ToLower()).Request.Params[1]; + var item = ((JArray)json).Select(p => RpcTransferOut.FromJson((JObject)p, rpc.protocolSettings)); + Assert.AreEqual(json.ToString(), ((JArray)item.Select(p => p.ToJson(rpc.protocolSettings)).ToArray()).ToString()); + } + + [TestMethod()] + public void TestRpcValidateAddressResult() + { + JToken json = TestUtils.RpcTestCases.Find(p => p.Name == nameof(RpcClient.ValidateAddressAsync).ToLower()).Response.Result; + var item = RpcValidateAddressResult.FromJson((JObject)json); + Assert.AreEqual(json.ToString(), item.ToJson().ToString()); + } + + [TestMethod()] + public void TestRpcValidator() + { + JToken json = TestUtils.RpcTestCases.Find(p => p.Name == nameof(RpcClient.GetNextBlockValidatorsAsync).ToLower()).Response.Result; + var item = ((JArray)json).Select(p => RpcValidator.FromJson((JObject)p)); + Assert.AreEqual(json.ToString(), ((JArray)item.Select(p => p.ToJson()).ToArray()).ToString()); + } + + [TestMethod()] + public void TestRpcVersion() + { + JToken json = TestUtils.RpcTestCases.Find(p => p.Name == nameof(RpcClient.GetVersionAsync).ToLower()).Response.Result; + var item = RpcVersion.FromJson((JObject)json); + Assert.AreEqual(json.ToString(), item.ToJson().ToString()); + } + } +} diff --git a/tests/Neo.Network.RPC.Tests/UT_TransactionManager.cs b/tests/Neo.Network.RPC.Tests/UT_TransactionManager.cs new file mode 100644 index 0000000000..ae025cf65a --- /dev/null +++ b/tests/Neo.Network.RPC.Tests/UT_TransactionManager.cs @@ -0,0 +1,267 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_TransactionManager.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Neo.Cryptography; +using Neo.Cryptography.ECC; +using Neo.IO; +using Neo.Json; +using Neo.Network.P2P; +using Neo.Network.P2P.Payloads; +using Neo.Network.RPC.Models; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using System; +using System.Linq; +using System.Numerics; +using System.Threading.Tasks; + +namespace Neo.Network.RPC.Tests +{ + [TestClass] + public class UT_TransactionManager + { + TransactionManager txManager; + Mock rpcClientMock; + Mock multiSigMock; + KeyPair keyPair1; + KeyPair keyPair2; + UInt160 sender; + UInt160 multiHash; + RpcClient client; + + [TestInitialize] + public void TestSetup() + { + keyPair1 = new KeyPair(Wallet.GetPrivateKeyFromWIF("KyXwTh1hB76RRMquSvnxZrJzQx7h9nQP2PCRL38v6VDb5ip3nf1p")); + keyPair2 = new KeyPair(Wallet.GetPrivateKeyFromWIF("L2LGkrwiNmUAnWYb1XGd5mv7v2eDf6P4F3gHyXSrNJJR4ArmBp7Q")); + sender = Contract.CreateSignatureRedeemScript(keyPair1.PublicKey).ToScriptHash(); + multiHash = Contract.CreateMultiSigContract(2, new ECPoint[] { keyPair1.PublicKey, keyPair2.PublicKey }).ScriptHash; + rpcClientMock = MockRpcClient(sender, new byte[1]); + client = rpcClientMock.Object; + multiSigMock = MockMultiSig(multiHash, new byte[1]); + } + + public static Mock MockRpcClient(UInt160 sender, byte[] script) + { + var mockRpc = new Mock(MockBehavior.Strict, new Uri("http://seed1.neo.org:10331"), null, null, null); + + // MockHeight + mockRpc.Setup(p => p.RpcSendAsync("getblockcount")).ReturnsAsync(100).Verifiable(); + + // calculatenetworkfee + var networkfee = new JObject(); + networkfee["networkfee"] = 100000000; + mockRpc.Setup(p => p.RpcSendAsync("calculatenetworkfee", It.Is(u => true))) + .ReturnsAsync(networkfee) + .Verifiable(); + + // MockGasBalance + byte[] balanceScript = NativeContract.GAS.Hash.MakeScript("balanceOf", sender); + var balanceResult = new ContractParameter() { Type = ContractParameterType.Integer, Value = BigInteger.Parse("10000000000000000") }; + + MockInvokeScript(mockRpc, balanceScript, balanceResult); + + // MockFeePerByte + byte[] policyScript = NativeContract.Policy.Hash.MakeScript("getFeePerByte"); + var policyResult = new ContractParameter() { Type = ContractParameterType.Integer, Value = BigInteger.Parse("1000") }; + + MockInvokeScript(mockRpc, policyScript, policyResult); + + // MockGasConsumed + var result = new ContractParameter(); + MockInvokeScript(mockRpc, script, result); + + return mockRpc; + } + + public static Mock MockMultiSig(UInt160 multiHash, byte[] script) + { + var mockRpc = new Mock(MockBehavior.Strict, new Uri("http://seed1.neo.org:10331"), null, null, null); + + // MockHeight + mockRpc.Setup(p => p.RpcSendAsync("getblockcount")).ReturnsAsync(100).Verifiable(); + + // calculatenetworkfee + var networkfee = new JObject(); + networkfee["networkfee"] = 100000000; + mockRpc.Setup(p => p.RpcSendAsync("calculatenetworkfee", It.Is(u => true))) + .ReturnsAsync(networkfee) + .Verifiable(); + + // MockGasBalance + byte[] balanceScript = NativeContract.GAS.Hash.MakeScript("balanceOf", multiHash); + var balanceResult = new ContractParameter() { Type = ContractParameterType.Integer, Value = BigInteger.Parse("10000000000000000") }; + + MockInvokeScript(mockRpc, balanceScript, balanceResult); + + // MockFeePerByte + byte[] policyScript = NativeContract.Policy.Hash.MakeScript("getFeePerByte"); + var policyResult = new ContractParameter() { Type = ContractParameterType.Integer, Value = BigInteger.Parse("1000") }; + + MockInvokeScript(mockRpc, policyScript, policyResult); + + // MockGasConsumed + var result = new ContractParameter(); + MockInvokeScript(mockRpc, script, result); + + return mockRpc; + } + + public static void MockInvokeScript(Mock mockClient, byte[] script, params ContractParameter[] parameters) + { + var result = new RpcInvokeResult() + { + Stack = parameters.Select(p => p.ToStackItem()).ToArray(), + GasConsumed = 100, + Script = Convert.ToBase64String(script), + State = VMState.HALT + }; + + mockClient.Setup(p => p.RpcSendAsync("invokescript", It.Is(j => + Convert.FromBase64String(j[0].AsString()).SequenceEqual(script)))) + .ReturnsAsync(result.ToJson()) + .Verifiable(); + } + + [TestMethod] + public async Task TestMakeTransaction() + { + Signer[] signers = new Signer[1] + { + new Signer + { + Account = sender, + Scopes= WitnessScope.Global + } + }; + + byte[] script = new byte[1]; + txManager = await TransactionManager.MakeTransactionAsync(rpcClientMock.Object, script, signers); + + var tx = txManager.Tx; + Assert.AreEqual(WitnessScope.Global, tx.Signers[0].Scopes); + } + + [TestMethod] + public async Task TestSign() + { + Signer[] signers = new Signer[1] + { + new Signer + { + Account = sender, + Scopes = WitnessScope.Global + } + }; + + byte[] script = new byte[1]; + txManager = await TransactionManager.MakeTransactionAsync(client, script, signers); + await txManager + .AddSignature(keyPair1) + .SignAsync(); + + // get signature from Witnesses + var tx = txManager.Tx; + ReadOnlyMemory signature = tx.Witnesses[0].InvocationScript[2..]; + + Assert.IsTrue(Crypto.VerifySignature(tx.GetSignData(client.protocolSettings.Network), signature.Span, keyPair1.PublicKey)); + // verify network fee and system fee + Assert.AreEqual(100000000/*Mock*/, tx.NetworkFee); + Assert.AreEqual(100, tx.SystemFee); + + // duplicate sign should not add new witness + await ThrowsAsync(async () => await txManager.AddSignature(keyPair1).SignAsync()); + Assert.AreEqual(null, txManager.Tx.Witnesses); + + // throw exception when the KeyPair is wrong + await ThrowsAsync(async () => await txManager.AddSignature(keyPair2).SignAsync()); + } + + // https://docs.microsoft.com/en-us/archive/msdn-magazine/2014/november/async-programming-unit-testing-asynchronous-code#testing-exceptions + static async Task ThrowsAsync(Func action, bool allowDerivedTypes = true) + where TException : Exception + { + try + { + await action(); + } + catch (Exception ex) + { + if (allowDerivedTypes && !(ex is TException)) + throw new Exception("Delegate threw exception of type " + + ex.GetType().Name + ", but " + typeof(TException).Name + + " or a derived type was expected.", ex); + if (!allowDerivedTypes && ex.GetType() != typeof(TException)) + throw new Exception("Delegate threw exception of type " + + ex.GetType().Name + ", but " + typeof(TException).Name + + " was expected.", ex); + return (TException)ex; + } + throw new Exception("Delegate did not throw expected exception " + + typeof(TException).Name + "."); + } + + [TestMethod] + public async Task TestSignMulti() + { + // Cosigner needs multi signature + Signer[] signers = new Signer[1] + { + new Signer + { + Account = multiHash, + Scopes = WitnessScope.Global + } + }; + + byte[] script = new byte[1]; + txManager = await TransactionManager.MakeTransactionAsync(multiSigMock.Object, script, signers); + await txManager + .AddMultiSig(keyPair1, 2, keyPair1.PublicKey, keyPair2.PublicKey) + .AddMultiSig(keyPair2, 2, keyPair1.PublicKey, keyPair2.PublicKey) + .SignAsync(); + } + + [TestMethod] + public async Task TestAddWitness() + { + // Cosigner as contract scripthash + Signer[] signers = new Signer[2] + { + new Signer + { + Account = sender, + Scopes = WitnessScope.Global + }, + new Signer + { + Account = UInt160.Zero, + Scopes = WitnessScope.Global + } + }; + + byte[] script = new byte[1]; + txManager = await TransactionManager.MakeTransactionAsync(rpcClientMock.Object, script, signers); + txManager.AddWitness(UInt160.Zero); + txManager.AddSignature(keyPair1); + await txManager.SignAsync(); + + var tx = txManager.Tx; + Assert.AreEqual(2, tx.Witnesses.Length); + Assert.AreEqual(40, tx.Witnesses[0].VerificationScript.Length); + Assert.AreEqual(66, tx.Witnesses[0].InvocationScript.Length); + } + } +} diff --git a/tests/Neo.Network.RPC.Tests/UT_Utility.cs b/tests/Neo.Network.RPC.Tests/UT_Utility.cs new file mode 100644 index 0000000000..fa410c5437 --- /dev/null +++ b/tests/Neo.Network.RPC.Tests/UT_Utility.cs @@ -0,0 +1,87 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Utility.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.SmartContract; +using Neo.Wallets; +using System; +using System.Numerics; + +namespace Neo.Network.RPC.Tests +{ + [TestClass] + public class UT_Utility + { + private KeyPair keyPair; + private UInt160 scriptHash; + private ProtocolSettings protocolSettings; + + [TestInitialize] + public void TestSetup() + { + keyPair = new KeyPair(Wallet.GetPrivateKeyFromWIF("KyXwTh1hB76RRMquSvnxZrJzQx7h9nQP2PCRL38v6VDb5ip3nf1p")); + scriptHash = Contract.CreateSignatureRedeemScript(keyPair.PublicKey).ToScriptHash(); + protocolSettings = ProtocolSettings.Load("protocol.json"); + } + + [TestMethod] + public void TestGetKeyPair() + { + string nul = null; + Assert.ThrowsException(() => Utility.GetKeyPair(nul)); + + string wif = "KyXwTh1hB76RRMquSvnxZrJzQx7h9nQP2PCRL38v6VDb5ip3nf1p"; + var result = Utility.GetKeyPair(wif); + Assert.AreEqual(keyPair, result); + + string privateKey = keyPair.PrivateKey.ToHexString(); + result = Utility.GetKeyPair(privateKey); + Assert.AreEqual(keyPair, result); + } + + [TestMethod] + public void TestGetScriptHash() + { + string nul = null; + Assert.ThrowsException(() => Utility.GetScriptHash(nul, protocolSettings)); + + string addr = scriptHash.ToAddress(protocolSettings.AddressVersion); + var result = Utility.GetScriptHash(addr, protocolSettings); + Assert.AreEqual(scriptHash, result); + + string hash = scriptHash.ToString(); + result = Utility.GetScriptHash(hash, protocolSettings); + Assert.AreEqual(scriptHash, result); + + string publicKey = keyPair.PublicKey.ToString(); + result = Utility.GetScriptHash(publicKey, protocolSettings); + Assert.AreEqual(scriptHash, result); + } + + [TestMethod] + public void TestToBigInteger() + { + decimal amount = 1.23456789m; + uint decimals = 9; + var result = amount.ToBigInteger(decimals); + Assert.AreEqual(1234567890, result); + + amount = 1.23456789m; + decimals = 18; + result = amount.ToBigInteger(decimals); + Assert.AreEqual(BigInteger.Parse("1234567890000000000"), result); + + amount = 1.23456789m; + decimals = 4; + Assert.ThrowsException(() => result = amount.ToBigInteger(decimals)); + } + } +} diff --git a/tests/Neo.Network.RPC.Tests/UT_WalletAPI.cs b/tests/Neo.Network.RPC.Tests/UT_WalletAPI.cs new file mode 100644 index 0000000000..10a35fd064 --- /dev/null +++ b/tests/Neo.Network.RPC.Tests/UT_WalletAPI.cs @@ -0,0 +1,181 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_WalletAPI.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Neo.Cryptography.ECC; +using Neo.Json; +using Neo.Network.P2P.Payloads; +using Neo.Network.RPC.Models; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using System.Linq; +using System.Numerics; +using System.Threading.Tasks; + +namespace Neo.Network.RPC.Tests +{ + [TestClass] + public class UT_WalletAPI + { + Mock rpcClientMock; + KeyPair keyPair1; + string address1; + UInt160 sender; + WalletAPI walletAPI; + UInt160 multiSender; + RpcClient client; + + [TestInitialize] + public void TestSetup() + { + keyPair1 = new KeyPair(Wallet.GetPrivateKeyFromWIF("KyXwTh1hB76RRMquSvnxZrJzQx7h9nQP2PCRL38v6VDb5ip3nf1p")); + sender = Contract.CreateSignatureRedeemScript(keyPair1.PublicKey).ToScriptHash(); + multiSender = Contract.CreateMultiSigContract(1, new ECPoint[] { keyPair1.PublicKey }).ScriptHash; + rpcClientMock = UT_TransactionManager.MockRpcClient(sender, new byte[0]); + client = rpcClientMock.Object; + address1 = Wallets.Helper.ToAddress(sender, client.protocolSettings.AddressVersion); + walletAPI = new WalletAPI(rpcClientMock.Object); + } + + [TestMethod] + public async Task TestGetUnclaimedGas() + { + byte[] testScript = NativeContract.NEO.Hash.MakeScript("unclaimedGas", sender, 99); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1_10000000) }); + + var balance = await walletAPI.GetUnclaimedGasAsync(address1); + Assert.AreEqual(1.1m, balance); + } + + [TestMethod] + public async Task TestGetNeoBalance() + { + byte[] testScript = NativeContract.NEO.Hash.MakeScript("balanceOf", sender); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1_00000000) }); + + var balance = await walletAPI.GetNeoBalanceAsync(address1); + Assert.AreEqual(1_00000000u, balance); + } + + [TestMethod] + public async Task TestGetGasBalance() + { + byte[] testScript = NativeContract.GAS.Hash.MakeScript("balanceOf", sender); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1_10000000) }); + + var balance = await walletAPI.GetGasBalanceAsync(address1); + Assert.AreEqual(1.1m, balance); + } + + [TestMethod] + public async Task TestGetTokenBalance() + { + byte[] testScript = UInt160.Zero.MakeScript("balanceOf", sender); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1_10000000) }); + + var balance = await walletAPI.GetTokenBalanceAsync(UInt160.Zero.ToString(), address1); + Assert.AreEqual(1_10000000, balance); + } + + [TestMethod] + public async Task TestClaimGas() + { + byte[] balanceScript = NativeContract.NEO.Hash.MakeScript("balanceOf", sender); + UT_TransactionManager.MockInvokeScript(rpcClientMock, balanceScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1_00000000) }); + + byte[] testScript = NativeContract.NEO.Hash.MakeScript("transfer", sender, sender, new BigInteger(1_00000000), null); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1_10000000) }); + + var json = new JObject(); + json["hash"] = UInt256.Zero.ToString(); + rpcClientMock.Setup(p => p.RpcSendAsync("sendrawtransaction", It.IsAny())).ReturnsAsync(json); + + var tranaction = await walletAPI.ClaimGasAsync(keyPair1.Export(), false); + Assert.AreEqual(testScript.ToHexString(), tranaction.Script.Span.ToHexString()); + } + + [TestMethod] + public async Task TestTransfer() + { + byte[] decimalsScript = NativeContract.GAS.Hash.MakeScript("decimals"); + UT_TransactionManager.MockInvokeScript(rpcClientMock, decimalsScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(8) }); + + byte[] testScript = NativeContract.GAS.Hash.MakeScript("transfer", sender, UInt160.Zero, NativeContract.GAS.Factor * 100, null) + .Concat(new[] { (byte)OpCode.ASSERT }) + .ToArray(); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1_10000000) }); + + var json = new JObject(); + json["hash"] = UInt256.Zero.ToString(); + rpcClientMock.Setup(p => p.RpcSendAsync("sendrawtransaction", It.IsAny())).ReturnsAsync(json); + + var tranaction = await walletAPI.TransferAsync(NativeContract.GAS.Hash.ToString(), keyPair1.Export(), UInt160.Zero.ToAddress(client.protocolSettings.AddressVersion), 100, null, true); + Assert.AreEqual(testScript.ToHexString(), tranaction.Script.Span.ToHexString()); + } + + [TestMethod] + public async Task TestTransferfromMultiSigAccount() + { + byte[] balanceScript = NativeContract.GAS.Hash.MakeScript("balanceOf", multiSender); + var balanceResult = new ContractParameter() { Type = ContractParameterType.Integer, Value = BigInteger.Parse("10000000000000000") }; + + UT_TransactionManager.MockInvokeScript(rpcClientMock, balanceScript, balanceResult); + + byte[] decimalsScript = NativeContract.GAS.Hash.MakeScript("decimals"); + UT_TransactionManager.MockInvokeScript(rpcClientMock, decimalsScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(8) }); + + byte[] testScript = NativeContract.GAS.Hash.MakeScript("transfer", multiSender, UInt160.Zero, NativeContract.GAS.Factor * 100, null) + .Concat(new[] { (byte)OpCode.ASSERT }) + .ToArray(); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1_10000000) }); + + var json = new JObject(); + json["hash"] = UInt256.Zero.ToString(); + rpcClientMock.Setup(p => p.RpcSendAsync("sendrawtransaction", It.IsAny())).ReturnsAsync(json); + + var tranaction = await walletAPI.TransferAsync(NativeContract.GAS.Hash, 1, new[] { keyPair1.PublicKey }, new[] { keyPair1 }, UInt160.Zero, NativeContract.GAS.Factor * 100, null, true); + Assert.AreEqual(testScript.ToHexString(), tranaction.Script.Span.ToHexString()); + + try + { + tranaction = await walletAPI.TransferAsync(NativeContract.GAS.Hash, 2, new[] { keyPair1.PublicKey }, new[] { keyPair1 }, UInt160.Zero, NativeContract.GAS.Factor * 100, null, true); + Assert.Fail(); + } + catch (System.Exception e) + { + Assert.AreEqual(e.Message, $"Need at least 2 KeyPairs for signing!"); + } + + testScript = NativeContract.GAS.Hash.MakeScript("transfer", multiSender, UInt160.Zero, NativeContract.GAS.Factor * 100, string.Empty) + .Concat(new[] { (byte)OpCode.ASSERT }) + .ToArray(); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1_10000000) }); + + tranaction = await walletAPI.TransferAsync(NativeContract.GAS.Hash, 1, new[] { keyPair1.PublicKey }, new[] { keyPair1 }, UInt160.Zero, NativeContract.GAS.Factor * 100, string.Empty, true); + Assert.AreEqual(testScript.ToHexString(), tranaction.Script.Span.ToHexString()); + } + + [TestMethod] + public async Task TestWaitTransaction() + { + Transaction transaction = TestUtils.GetTransaction(); + rpcClientMock.Setup(p => p.RpcSendAsync("getrawtransaction", It.Is(j => j[0].AsString() == transaction.Hash.ToString()))) + .ReturnsAsync(new RpcTransaction { Transaction = transaction, VMState = VMState.HALT, BlockHash = UInt256.Zero, BlockTime = 100, Confirmations = 1 }.ToJson(client.protocolSettings)); + + var tx = await walletAPI.WaitTransactionAsync(transaction); + Assert.AreEqual(VMState.HALT, tx.VMState); + Assert.AreEqual(UInt256.Zero, tx.BlockHash); + } + } +} diff --git a/tests/Neo.Plugins.OracleService.Tests/Neo.Plugins.OracleService.Tests.csproj b/tests/Neo.Plugins.OracleService.Tests/Neo.Plugins.OracleService.Tests.csproj new file mode 100644 index 0000000000..4dd4abbfa4 --- /dev/null +++ b/tests/Neo.Plugins.OracleService.Tests/Neo.Plugins.OracleService.Tests.csproj @@ -0,0 +1,28 @@ + + + + net8.0 + OracleService.Tests + Neo.Plugins + + + + + + + + + + + + + + + + + PreserveNewest + PreserveNewest + + + + diff --git a/tests/Neo.Plugins.OracleService.Tests/TestBlockchain.cs b/tests/Neo.Plugins.OracleService.Tests/TestBlockchain.cs new file mode 100644 index 0000000000..d689578e22 --- /dev/null +++ b/tests/Neo.Plugins.OracleService.Tests/TestBlockchain.cs @@ -0,0 +1,36 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TestBlockchain.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Persistence; +using System; + +namespace Neo.Plugins.OracleService.Tests +{ + public static class TestBlockchain + { + public static readonly NeoSystem TheNeoSystem; + + static TestBlockchain() + { + Console.WriteLine("initialize NeoSystem"); + TheNeoSystem = new NeoSystem(ProtocolSettings.Load("config.json"), new MemoryStoreProvider()); + } + + public static void InitializeMockNeoSystem() + { + } + + internal static DataCache GetTestSnapshot() + { + return TheNeoSystem.GetSnapshot().CreateSnapshot(); + } + } +} diff --git a/tests/Neo.Plugins.OracleService.Tests/TestUtils.cs b/tests/Neo.Plugins.OracleService.Tests/TestUtils.cs new file mode 100644 index 0000000000..7f1c3a58d5 --- /dev/null +++ b/tests/Neo.Plugins.OracleService.Tests/TestUtils.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TestUtils.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using Neo.SmartContract; +using Neo.SmartContract.Native; + +namespace Neo.Plugins.OracleService.Tests +{ + public static class TestUtils + { + public static StorageKey CreateStorageKey(this NativeContract contract, byte prefix, ISerializable key) + { + var k = new KeyBuilder(contract.Id, prefix); + if (key != null) k = k.Add(key); + return k; + } + + public static StorageKey CreateStorageKey(this NativeContract contract, byte prefix, uint value) + { + return new KeyBuilder(contract.Id, prefix).AddBigEndian(value); + } + } +} diff --git a/tests/Neo.Plugins.OracleService.Tests/UT_OracleService.cs b/tests/Neo.Plugins.OracleService.Tests/UT_OracleService.cs new file mode 100644 index 0000000000..9622ceb9e2 --- /dev/null +++ b/tests/Neo.Plugins.OracleService.Tests/UT_OracleService.cs @@ -0,0 +1,118 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_OracleService.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Akka.TestKit.Xunit2; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography.ECC; +using Neo.Network.P2P.Payloads; +using Neo.SmartContract; +using Neo.SmartContract.Native; + +namespace Neo.Plugins.OracleService.Tests +{ + [TestClass] + public class UT_OracleService : TestKit + { + [TestInitialize] + public void TestSetup() + { + TestBlockchain.InitializeMockNeoSystem(); + } + + [TestMethod] + public void TestFilter() + { + var json = @"{ + ""Stores"": [ + ""Lambton Quay"", + ""Willis Street"" + ], + ""Manufacturers"": [ + { + ""Name"": ""Acme Co"", + ""Products"": [ + { + ""Name"": ""Anvil"", + ""Price"": 50 + } + ] + }, + { + ""Name"": ""Contoso"", + ""Products"": [ + { + ""Name"": ""Elbow Grease"", + ""Price"": 99.95 + }, + { + ""Name"": ""Headlight Fluid"", + ""Price"": 4 + } + ] + } + ] +}"; + + Assert.AreEqual(@"[""Acme Co""]", Utility.StrictUTF8.GetString(OracleService.Filter(json, "$.Manufacturers[0].Name"))); + Assert.AreEqual("[50]", Utility.StrictUTF8.GetString(OracleService.Filter(json, "$.Manufacturers[0].Products[0].Price"))); + Assert.AreEqual(@"[""Elbow Grease""]", Utility.StrictUTF8.GetString(OracleService.Filter(json, "$.Manufacturers[1].Products[0].Name"))); + Assert.AreEqual(@"[{""Name"":""Elbow Grease"",""Price"":99.95}]", Utility.StrictUTF8.GetString(OracleService.Filter(json, "$.Manufacturers[1].Products[0]"))); + } + + [TestMethod] + public void TestCreateOracleResponseTx() + { + var snapshot = TestBlockchain.GetTestSnapshot(); + + var executionFactor = NativeContract.Policy.GetExecFeeFactor(snapshot); + Assert.AreEqual(executionFactor, (uint)30); + var feePerByte = NativeContract.Policy.GetFeePerByte(snapshot); + Assert.AreEqual(feePerByte, 1000); + + OracleRequest request = new OracleRequest + { + OriginalTxid = UInt256.Zero, + GasForResponse = 100000000 * 1, + Url = "https://127.0.0.1/test", + Filter = "", + CallbackContract = UInt160.Zero, + CallbackMethod = "callback", + UserData = System.Array.Empty() + }; + byte Prefix_Transaction = 11; + snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_Transaction, request.OriginalTxid), new StorageItem(new TransactionState() + { + BlockIndex = 1, + Transaction = new Transaction() + { + ValidUntilBlock = 1 + } + })); + OracleResponse response = new OracleResponse() { Id = 1, Code = OracleResponseCode.Success, Result = new byte[] { 0x00 } }; + ECPoint[] oracleNodes = new ECPoint[] { ECCurve.Secp256r1.G }; + var tx = OracleService.CreateResponseTx(snapshot, request, response, oracleNodes, ProtocolSettings.Default); + + Assert.AreEqual(166, tx.Size); + Assert.AreEqual(2198650, tx.NetworkFee); + Assert.AreEqual(97801350, tx.SystemFee); + + // case (2) The size of attribute exceed the maximum limit + + request.GasForResponse = 0_10000000; + response.Result = new byte[10250]; + tx = OracleService.CreateResponseTx(snapshot, request, response, oracleNodes, ProtocolSettings.Default); + Assert.AreEqual(165, tx.Size); + Assert.AreEqual(OracleResponseCode.InsufficientFunds, response.Code); + Assert.AreEqual(2197650, tx.NetworkFee); + Assert.AreEqual(7802350, tx.SystemFee); + } + } +} diff --git a/tests/Neo.Plugins.OracleService.Tests/config.json b/tests/Neo.Plugins.OracleService.Tests/config.json new file mode 100644 index 0000000000..67bc3a62fa --- /dev/null +++ b/tests/Neo.Plugins.OracleService.Tests/config.json @@ -0,0 +1,74 @@ +{ + "ApplicationConfiguration": { + "Logger": { + "Path": "Logs", + "ConsoleOutput": false, + "Active": false + }, + "Storage": { + "Engine": "LevelDBStore", // Candidates [MemoryStore, LevelDBStore, RocksDBStore] + "Path": "Data_LevelDB_{0}" // {0} is a placeholder for the network id + }, + "P2P": { + "Port": 10333, + "MinDesiredConnections": 10, + "MaxConnections": 40, + "MaxConnectionsPerAddress": 3 + }, + "UnlockWallet": { + "Path": "", + "Password": "", + "IsActive": false + }, + "Contracts": { + "NeoNameService": "0x50ac1c37690cc2cfc594472833cf57505d5f46de" + }, + "Plugins": { + "DownloadUrl": "https://api.github.com/repos/neo-project/neo/releases" + } + }, + "ProtocolConfiguration": { + "Network": 860833102, + "AddressVersion": 53, + "MillisecondsPerBlock": 15000, + "MaxTransactionsPerBlock": 512, + "MemoryPoolMaxTransactions": 50000, + "MaxTraceableBlocks": 2102400, + "Hardforks": { + "HF_Aspidochelone": 1730000, + "HF_Basilisk": 4120000 + }, + "InitialGasDistribution": 5200000000000000, + "ValidatorsCount": 7, + "StandbyCommittee": [ + "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", + "02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093", + "03b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a", + "02ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba554", + "024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d", + "02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e", + "02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70", + "023a36c72844610b4d34d1968662424011bf783ca9d984efa19a20babf5582f3fe", + "03708b860c1de5d87f5b151a12c2a99feebd2e8b315ee8e7cf8aa19692a9e18379", + "03c6aa6e12638b36e88adc1ccdceac4db9929575c3e03576c617c49cce7114a050", + "03204223f8c86b8cd5c89ef12e4f0dbb314172e9241e30c9ef2293790793537cf0", + "02a62c915cf19c7f19a50ec217e79fac2439bbaad658493de0c7d8ffa92ab0aa62", + "03409f31f0d66bdc2f70a9730b66fe186658f84a8018204db01c106edc36553cd0", + "0288342b141c30dc8ffcde0204929bb46aed5756b41ef4a56778d15ada8f0c6654", + "020f2887f41474cfeb11fd262e982051c1541418137c02a0f4961af911045de639", + "0222038884bbd1d8ff109ed3bdef3542e768eef76c1247aea8bc8171f532928c30", + "03d281b42002647f0113f36c7b8efb30db66078dfaaa9ab3ff76d043a98d512fde", + "02504acbc1f4b3bdad1d86d6e1a08603771db135a73e61c9d565ae06a1938cd2ad", + "0226933336f1b75baa42d42b71d9091508b638046d19abd67f4e119bf64a7cfb4d", + "03cdcea66032b82f5c30450e381e5295cae85c5e6943af716cc6b646352a6067dc", + "02cd5a5547119e24feaa7c2a0f37b8c9366216bab7054de0065c9be42084003c8a" + ], + "SeedList": [ + "seed1.neo.org:10333", + "seed2.neo.org:10333", + "seed3.neo.org:10333", + "seed4.neo.org:10333", + "seed5.neo.org:10333" + ] + } +} diff --git a/tests/Neo.Plugins.RpcServer.Tests/MockNeoSystem.cs b/tests/Neo.Plugins.RpcServer.Tests/MockNeoSystem.cs new file mode 100644 index 0000000000..0f45a63d98 --- /dev/null +++ b/tests/Neo.Plugins.RpcServer.Tests/MockNeoSystem.cs @@ -0,0 +1,34 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// MockNeoSystem.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Ledger; +using Neo.Persistence; + +namespace Neo.Plugins.RpcServer.Tests +{ + public class MockNeoSystem : NeoSystem + { + public SnapshotCache SnapshotCache { get; } + public MemoryPool MemoryPool { get; } + + public MockNeoSystem(SnapshotCache snapshotCache, MemoryPool memoryPool) + : base(TestProtocolSettings.Default, new TestBlockchain.StoreProvider()) + { + SnapshotCache = snapshotCache; + MemoryPool = memoryPool; + } + + public SnapshotCache GetSnapshot() + { + return SnapshotCache; + } + } +} diff --git a/tests/Neo.Plugins.RpcServer.Tests/Neo.Plugins.RpcServer.Tests.csproj b/tests/Neo.Plugins.RpcServer.Tests/Neo.Plugins.RpcServer.Tests.csproj new file mode 100644 index 0000000000..5b693c96b8 --- /dev/null +++ b/tests/Neo.Plugins.RpcServer.Tests/Neo.Plugins.RpcServer.Tests.csproj @@ -0,0 +1,21 @@ + + + + net8.0 + Neo.Plugins.RpcServer.Tests + Neo.Plugins.RpcServer.Tests + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Neo.Plugins.RpcServer.Tests/TestBlockchain.cs b/tests/Neo.Plugins.RpcServer.Tests/TestBlockchain.cs new file mode 100644 index 0000000000..f6e35cf695 --- /dev/null +++ b/tests/Neo.Plugins.RpcServer.Tests/TestBlockchain.cs @@ -0,0 +1,49 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TestBlockchain.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Akka.Actor; +using Neo.Ledger; +using Neo.Persistence; +using System; + +namespace Neo.Plugins.RpcServer.Tests +{ + public static class TestBlockchain + { + public static readonly NeoSystem TheNeoSystem; + public static readonly UInt160[] DefaultExtensibleWitnessWhiteList; + private static readonly MemoryStore Store = new(); + + internal class StoreProvider : IStoreProvider + { + public string Name => "TestProvider"; + + public IStore GetStore(string path) => Store; + } + + static TestBlockchain() + { + Console.WriteLine("initialize NeoSystem"); + TheNeoSystem = new NeoSystem(TestProtocolSettings.Default, new StoreProvider()); + } + + internal static void ResetStore() + { + Store.Reset(); + TheNeoSystem.Blockchain.Ask(new Blockchain.Initialize()).Wait(); + } + + internal static DataCache GetTestSnapshot() + { + return TheNeoSystem.GetSnapshot().CreateSnapshot(); + } + } +} diff --git a/tests/Neo.Plugins.RpcServer.Tests/TestProtocolSettings.cs b/tests/Neo.Plugins.RpcServer.Tests/TestProtocolSettings.cs new file mode 100644 index 0000000000..edf2df39fb --- /dev/null +++ b/tests/Neo.Plugins.RpcServer.Tests/TestProtocolSettings.cs @@ -0,0 +1,65 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TestProtocolSettings.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Cryptography.ECC; + +namespace Neo.Plugins.RpcServer.Tests +{ + public static class TestProtocolSettings + { + public static readonly ProtocolSettings Default = new() + { + Network = 0x334F454Eu, + AddressVersion = ProtocolSettings.Default.AddressVersion, + StandbyCommittee = new[] + { + //Validators + ECPoint.Parse("03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", ECCurve.Secp256r1), + ECPoint.Parse("02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093", ECCurve.Secp256r1), + ECPoint.Parse("03b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a", ECCurve.Secp256r1), + ECPoint.Parse("02ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba554", ECCurve.Secp256r1), + ECPoint.Parse("024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d", ECCurve.Secp256r1), + ECPoint.Parse("02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e", ECCurve.Secp256r1), + ECPoint.Parse("02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70", ECCurve.Secp256r1), + //Other Members + ECPoint.Parse("023a36c72844610b4d34d1968662424011bf783ca9d984efa19a20babf5582f3fe", ECCurve.Secp256r1), + ECPoint.Parse("03708b860c1de5d87f5b151a12c2a99feebd2e8b315ee8e7cf8aa19692a9e18379", ECCurve.Secp256r1), + ECPoint.Parse("03c6aa6e12638b36e88adc1ccdceac4db9929575c3e03576c617c49cce7114a050", ECCurve.Secp256r1), + ECPoint.Parse("03204223f8c86b8cd5c89ef12e4f0dbb314172e9241e30c9ef2293790793537cf0", ECCurve.Secp256r1), + ECPoint.Parse("02a62c915cf19c7f19a50ec217e79fac2439bbaad658493de0c7d8ffa92ab0aa62", ECCurve.Secp256r1), + ECPoint.Parse("03409f31f0d66bdc2f70a9730b66fe186658f84a8018204db01c106edc36553cd0", ECCurve.Secp256r1), + ECPoint.Parse("0288342b141c30dc8ffcde0204929bb46aed5756b41ef4a56778d15ada8f0c6654", ECCurve.Secp256r1), + ECPoint.Parse("020f2887f41474cfeb11fd262e982051c1541418137c02a0f4961af911045de639", ECCurve.Secp256r1), + ECPoint.Parse("0222038884bbd1d8ff109ed3bdef3542e768eef76c1247aea8bc8171f532928c30", ECCurve.Secp256r1), + ECPoint.Parse("03d281b42002647f0113f36c7b8efb30db66078dfaaa9ab3ff76d043a98d512fde", ECCurve.Secp256r1), + ECPoint.Parse("02504acbc1f4b3bdad1d86d6e1a08603771db135a73e61c9d565ae06a1938cd2ad", ECCurve.Secp256r1), + ECPoint.Parse("0226933336f1b75baa42d42b71d9091508b638046d19abd67f4e119bf64a7cfb4d", ECCurve.Secp256r1), + ECPoint.Parse("03cdcea66032b82f5c30450e381e5295cae85c5e6943af716cc6b646352a6067dc", ECCurve.Secp256r1), + ECPoint.Parse("02cd5a5547119e24feaa7c2a0f37b8c9366216bab7054de0065c9be42084003c8a", ECCurve.Secp256r1) + }, + ValidatorsCount = 7, + SeedList = new[] + { + "seed1.neo.org:10333", + "seed2.neo.org:10333", + "seed3.neo.org:10333", + "seed4.neo.org:10333", + "seed5.neo.org:10333" + }, + MillisecondsPerBlock = ProtocolSettings.Default.MillisecondsPerBlock, + MaxTransactionsPerBlock = ProtocolSettings.Default.MaxTransactionsPerBlock, + MemoryPoolMaxTransactions = ProtocolSettings.Default.MemoryPoolMaxTransactions, + MaxTraceableBlocks = ProtocolSettings.Default.MaxTraceableBlocks, + InitialGasDistribution = ProtocolSettings.Default.InitialGasDistribution, + Hardforks = ProtocolSettings.Default.Hardforks + }; + } +} diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_Result.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_Result.cs new file mode 100644 index 0000000000..aa6a8fe308 --- /dev/null +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_Result.cs @@ -0,0 +1,26 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Result.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.SmartContract; + +namespace Neo.Plugins.RpcServer.Tests; + +[TestClass] +public class UT_Result +{ + [TestMethod] + public void TestNotNull_Or() + { + ContractState? contracts = null; + Assert.ThrowsException(() => contracts.NotNull_Or(RpcError.UnknownContract).ToJson()); + } +} diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcError.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcError.cs new file mode 100644 index 0000000000..d3c43a5458 --- /dev/null +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcError.cs @@ -0,0 +1,48 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_RpcError.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Neo.Plugins.RpcServer.Tests +{ + [TestClass] + public class UT_RpcError + { + [TestMethod] + public void AllDifferent() + { + HashSet codes = new(); + + foreach (RpcError error in typeof(RpcError) + .GetFields(BindingFlags.Static | BindingFlags.Public) + .Where(u => u.DeclaringType == typeof(RpcError)) + .Select(u => u.GetValue(null)) + .Cast()) + { + Assert.IsTrue(codes.Add(error.ToString())); + + if (error.Code == RpcError.WalletFeeLimit.Code) + Assert.IsNotNull(error.Data); + else + Assert.IsNull(error.Data); + } + } + + [TestMethod] + public void TestJson() + { + Assert.AreEqual("{\"code\":-600,\"message\":\"Access denied\"}", RpcError.AccessDenied.ToJson().ToString(false)); + } + } +} diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.cs new file mode 100644 index 0000000000..7bca550b83 --- /dev/null +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.cs @@ -0,0 +1,61 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_RpcServer.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.AspNetCore.Http; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using Neo.Ledger; +using Neo.Persistence; +using System; +using System.Text; + +namespace Neo.Plugins.RpcServer.Tests +{ + [TestClass] + public partial class UT_RpcServer + { + private Mock _systemMock; + private SnapshotCache _snapshotCache; + private MemoryPool _memoryPool; + private RpcServerSettings _settings; + private RpcServer _rpcServer; + + [TestInitialize] + public void TestSetup() + { + // Mock IReadOnlyStore + var mockStore = new Mock(); + + // Initialize SnapshotCache with the mock IReadOnlyStore + _snapshotCache = new SnapshotCache(mockStore.Object); + + // Initialize NeoSystem + var neoSystem = new NeoSystem(TestProtocolSettings.Default, new TestBlockchain.StoreProvider()); + + // Initialize MemoryPool with the NeoSystem + _memoryPool = new MemoryPool(neoSystem); + + // Set up the mock system with the correct constructor arguments + _systemMock = new Mock(_snapshotCache, _memoryPool); + + _rpcServer = new RpcServer(_systemMock.Object, RpcServerSettings.Default); + } + + [TestMethod] + public void TestCheckAuth_ValidCredentials_ReturnsTrue() + { + var context = new DefaultHttpContext(); + context.Request.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes("testuser:testpass")); + var result = _rpcServer.CheckAuth(context); + Assert.IsTrue(result); + } + } +} diff --git a/tests/Neo.Plugins.Storage.Tests/Neo.Plugins.Storage.Tests.csproj b/tests/Neo.Plugins.Storage.Tests/Neo.Plugins.Storage.Tests.csproj new file mode 100644 index 0000000000..3e06cf32ca --- /dev/null +++ b/tests/Neo.Plugins.Storage.Tests/Neo.Plugins.Storage.Tests.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + Neo.Plugins.Storage.Tests + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Neo.Plugins.Storage.Tests/StoreTest.cs b/tests/Neo.Plugins.Storage.Tests/StoreTest.cs new file mode 100644 index 0000000000..2773f92e80 --- /dev/null +++ b/tests/Neo.Plugins.Storage.Tests/StoreTest.cs @@ -0,0 +1,200 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// StoreTest.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Persistence; +using System.IO; +using System.Linq; + +namespace Neo.Plugins.Storage.Tests +{ + [TestClass] + public class StoreTest + { + private const string path_leveldb = "Data_LevelDB_UT"; + private const string path_rocksdb = "Data_RocksDB_UT"; + + [TestInitialize] + public void OnStart() + { + if (Directory.Exists(path_leveldb)) Directory.Delete(path_leveldb, true); + if (Directory.Exists(path_rocksdb)) Directory.Delete(path_rocksdb, true); + } + + [TestMethod] + public void TestMemory() + { + using var store = new MemoryStore(); + TestPersistenceDelete(store); + // Test all with the same store + + TestStorage(store); + + // Test with different storages + + TestPersistenceWrite(store); + TestPersistenceRead(store, true); + TestPersistenceDelete(store); + TestPersistenceRead(store, false); + } + + [TestMethod] + public void TestLevelDb() + { + using var plugin = new LevelDBStore(); + TestPersistenceDelete(plugin.GetStore(path_leveldb)); + // Test all with the same store + + TestStorage(plugin.GetStore(path_leveldb)); + + // Test with different storages + + TestPersistenceWrite(plugin.GetStore(path_leveldb)); + TestPersistenceRead(plugin.GetStore(path_leveldb), true); + TestPersistenceDelete(plugin.GetStore(path_leveldb)); + TestPersistenceRead(plugin.GetStore(path_leveldb), false); + } + + [TestMethod] + public void TestRocksDb() + { + using var plugin = new RocksDBStore(); + TestPersistenceDelete(plugin.GetStore(path_rocksdb)); + // Test all with the same store + + TestStorage(plugin.GetStore(path_rocksdb)); + + // Test with different storages + + TestPersistenceWrite(plugin.GetStore(path_rocksdb)); + TestPersistenceRead(plugin.GetStore(path_rocksdb), true); + TestPersistenceDelete(plugin.GetStore(path_rocksdb)); + TestPersistenceRead(plugin.GetStore(path_rocksdb), false); + } + + /// + /// Test Put/Delete/TryGet/Seek + /// + /// Store + private void TestStorage(IStore store) + { + using (store) + { + var key1 = new byte[] { 0x01, 0x02 }; + var value1 = new byte[] { 0x03, 0x04 }; + + store.Delete(key1); + var ret = store.TryGet(key1); + Assert.IsNull(ret); + + store.Put(key1, value1); + ret = store.TryGet(key1); + CollectionAssert.AreEqual(value1, ret); + Assert.IsTrue(store.Contains(key1)); + + ret = store.TryGet(value1); + Assert.IsNull(ret); + Assert.IsTrue(store.Contains(key1)); + + store.Delete(key1); + + ret = store.TryGet(key1); + Assert.IsNull(ret); + Assert.IsFalse(store.Contains(key1)); + + // Test seek in order + + store.Put(new byte[] { 0x00, 0x00, 0x04 }, new byte[] { 0x04 }); + store.Put(new byte[] { 0x00, 0x00, 0x00 }, new byte[] { 0x00 }); + store.Put(new byte[] { 0x00, 0x00, 0x01 }, new byte[] { 0x01 }); + store.Put(new byte[] { 0x00, 0x00, 0x02 }, new byte[] { 0x02 }); + store.Put(new byte[] { 0x00, 0x00, 0x03 }, new byte[] { 0x03 }); + + // Seek Forward + + var entries = store.Seek(new byte[] { 0x00, 0x00, 0x02 }, SeekDirection.Forward).ToArray(); + Assert.AreEqual(3, entries.Length); + CollectionAssert.AreEqual(new byte[] { 0x00, 0x00, 0x02 }, entries[0].Key); + CollectionAssert.AreEqual(new byte[] { 0x02 }, entries[0].Value); + CollectionAssert.AreEqual(new byte[] { 0x00, 0x00, 0x03 }, entries[1].Key); + CollectionAssert.AreEqual(new byte[] { 0x03 }, entries[1].Value); + CollectionAssert.AreEqual(new byte[] { 0x00, 0x00, 0x04 }, entries[2].Key); + CollectionAssert.AreEqual(new byte[] { 0x04 }, entries[2].Value); + + // Seek Backward + + entries = store.Seek(new byte[] { 0x00, 0x00, 0x02 }, SeekDirection.Backward).ToArray(); + Assert.AreEqual(3, entries.Length); + CollectionAssert.AreEqual(new byte[] { 0x00, 0x00, 0x02 }, entries[0].Key); + CollectionAssert.AreEqual(new byte[] { 0x02 }, entries[0].Value); + CollectionAssert.AreEqual(new byte[] { 0x00, 0x00, 0x01 }, entries[1].Key); + CollectionAssert.AreEqual(new byte[] { 0x01 }, entries[1].Value); + + // Seek Backward + store.Delete(new byte[] { 0x00, 0x00, 0x00 }); + store.Delete(new byte[] { 0x00, 0x00, 0x01 }); + store.Delete(new byte[] { 0x00, 0x00, 0x02 }); + store.Delete(new byte[] { 0x00, 0x00, 0x03 }); + store.Delete(new byte[] { 0x00, 0x00, 0x04 }); + store.Put(new byte[] { 0x00, 0x00, 0x00 }, new byte[] { 0x00 }); + store.Put(new byte[] { 0x00, 0x00, 0x01 }, new byte[] { 0x01 }); + store.Put(new byte[] { 0x00, 0x01, 0x02 }, new byte[] { 0x02 }); + + entries = store.Seek(new byte[] { 0x00, 0x00, 0x03 }, SeekDirection.Backward).ToArray(); + Assert.AreEqual(2, entries.Length); + CollectionAssert.AreEqual(new byte[] { 0x00, 0x00, 0x01 }, entries[0].Key); + CollectionAssert.AreEqual(new byte[] { 0x01 }, entries[0].Value); + CollectionAssert.AreEqual(new byte[] { 0x00, 0x00, 0x00 }, entries[1].Key); + CollectionAssert.AreEqual(new byte[] { 0x00 }, entries[1].Value); + } + } + + /// + /// Test Put + /// + /// Store + private void TestPersistenceWrite(IStore store) + { + using (store) + { + store.Put(new byte[] { 0x01, 0x02, 0x03 }, new byte[] { 0x04, 0x05, 0x06 }); + } + } + + /// + /// Test Put + /// + /// Store + private void TestPersistenceDelete(IStore store) + { + using (store) + { + store.Delete(new byte[] { 0x01, 0x02, 0x03 }); + } + } + + /// + /// Test Read + /// + /// Store + /// Should exist + private void TestPersistenceRead(IStore store, bool shouldExist) + { + using (store) + { + var ret = store.TryGet(new byte[] { 0x01, 0x02, 0x03 }); + + if (shouldExist) CollectionAssert.AreEqual(new byte[] { 0x04, 0x05, 0x06 }, ret); + else Assert.IsNull(ret); + } + } + } +} diff --git a/tests/Neo.UnitTests/Cryptography/ECC/UT_ECFieldElement.cs b/tests/Neo.UnitTests/Cryptography/ECC/UT_ECFieldElement.cs index 94fe8a77ec..3d4e4c56f2 100644 --- a/tests/Neo.UnitTests/Cryptography/ECC/UT_ECFieldElement.cs +++ b/tests/Neo.UnitTests/Cryptography/ECC/UT_ECFieldElement.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ECFieldElement.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; diff --git a/tests/Neo.UnitTests/Cryptography/ECC/UT_ECPoint.cs b/tests/Neo.UnitTests/Cryptography/ECC/UT_ECPoint.cs index 886677203d..4b0c7d484f 100644 --- a/tests/Neo.UnitTests/Cryptography/ECC/UT_ECPoint.cs +++ b/tests/Neo.UnitTests/Cryptography/ECC/UT_ECPoint.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ECPoint.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; @@ -164,7 +175,7 @@ public void TestEquals() point.Equals(null).Should().BeFalse(); point = new ECPoint(null, null, ECCurve.Secp256k1); - point.Equals(new ECPoint(null, null, ECCurve.Secp256r1)).Should().BeTrue(); + point.Equals(new ECPoint(null, null, ECCurve.Secp256r1)).Should().BeFalse(); point.Equals(ECCurve.Secp256r1.G).Should().BeFalse(); ECCurve.Secp256r1.G.Equals(point).Should().BeFalse(); @@ -188,7 +199,7 @@ public void TestEqualsObject() point.Equals(1u).Should().BeFalse(); point = new ECPoint(null, null, ECCurve.Secp256k1); - point.Equals(new ECPoint(null, null, ECCurve.Secp256r1)).Should().BeTrue(); + point.Equals(new ECPoint(null, null, ECCurve.Secp256r1)).Should().BeFalse(); point.Equals(ECCurve.Secp256r1.G).Should().BeFalse(); ECCurve.Secp256r1.G.Equals(point).Should().BeFalse(); diff --git a/tests/Neo.UnitTests/Cryptography/UT_Base58.cs b/tests/Neo.UnitTests/Cryptography/UT_Base58.cs index fbf29ba888..e7cb467dfa 100644 --- a/tests/Neo.UnitTests/Cryptography/UT_Base58.cs +++ b/tests/Neo.UnitTests/Cryptography/UT_Base58.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Base58.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; diff --git a/tests/Neo.UnitTests/Cryptography/UT_BloomFilter.cs b/tests/Neo.UnitTests/Cryptography/UT_BloomFilter.cs index 7ed470e3e1..907a5df2b9 100644 --- a/tests/Neo.UnitTests/Cryptography/UT_BloomFilter.cs +++ b/tests/Neo.UnitTests/Cryptography/UT_BloomFilter.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_BloomFilter.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; diff --git a/tests/Neo.UnitTests/Cryptography/UT_Crypto.cs b/tests/Neo.UnitTests/Cryptography/UT_Crypto.cs index cf54ed16ad..e311d6cafe 100644 --- a/tests/Neo.UnitTests/Cryptography/UT_Crypto.cs +++ b/tests/Neo.UnitTests/Cryptography/UT_Crypto.cs @@ -1,9 +1,19 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Crypto.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; using Neo.Wallets; using System; -using System.Linq; using System.Security.Cryptography; namespace Neo.UnitTests.Cryptography @@ -43,7 +53,7 @@ public void TestSetup() public void TestVerifySignature() { byte[] message = System.Text.Encoding.Default.GetBytes("HelloWorld"); - byte[] signature = Crypto.Sign(message, key.PrivateKey, key.PublicKey.EncodePoint(false).Skip(1).ToArray()); + byte[] signature = Crypto.Sign(message, key.PrivateKey); Crypto.VerifySignature(message, signature, key.PublicKey).Should().BeTrue(); byte[] wrongKey = new byte[33]; @@ -63,16 +73,16 @@ public void TestVerifySignature() [TestMethod] public void TestSecp256k1() { - byte[] message = System.Text.Encoding.Default.GetBytes("hello"); - byte[] signature = "5331be791532d157df5b5620620d938bcb622ad02c81cfc184c460efdad18e695480d77440c511e9ad02ea30d773cb54e88f8cbb069644aefa283957085f38b5".HexToBytes(); - byte[] pubKey = "03ea01cb94bdaf0cd1c01b159d474f9604f4af35a3e2196f6bdfdb33b2aa4961fa".HexToBytes(); + byte[] privkey = "7177f0d04c79fa0b8c91fe90c1cf1d44772d1fba6e5eb9b281a22cd3aafb51fe".HexToBytes(); + byte[] message = "2d46a712699bae19a634563d74d04cc2da497b841456da270dccb75ac2f7c4e7".HexToBytes(); + var signature = Crypto.Sign(message, privkey, Neo.Cryptography.ECC.ECCurve.Secp256k1); + byte[] pubKey = "04fd0a8c1ce5ae5570fdd46e7599c16b175bf0ebdfe9c178f1ab848fb16dac74a5d301b0534c7bcf1b3760881f0c420d17084907edd771e1c9c8e941bbf6ff9108".HexToBytes(); Crypto.VerifySignature(message, signature, pubKey, Neo.Cryptography.ECC.ECCurve.Secp256k1) .Should().BeTrue(); message = System.Text.Encoding.Default.GetBytes("world"); - signature = "b1e6ff4f40536fb7ed706b0f7567903cc227a5241a079fb86f3de51b8321c1e690f37ad0c788848605c1653567935845f0d35a8a1a37174dcbbd235caac8e969".HexToBytes(); - pubKey = "03661b86d54eb3a8e7ea2399e0db36ab65753f95fff661da53ae0121278b881ad0".HexToBytes(); + signature = Crypto.Sign(message, privkey, Neo.Cryptography.ECC.ECCurve.Secp256k1); Crypto.VerifySignature(message, signature, pubKey, Neo.Cryptography.ECC.ECCurve.Secp256k1) .Should().BeTrue(); diff --git a/tests/Neo.UnitTests/Cryptography/UT_Cryptography_Helper.cs b/tests/Neo.UnitTests/Cryptography/UT_Cryptography_Helper.cs index c2033e3fd7..01022baab9 100644 --- a/tests/Neo.UnitTests/Cryptography/UT_Cryptography_Helper.cs +++ b/tests/Neo.UnitTests/Cryptography/UT_Cryptography_Helper.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Cryptography_Helper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; @@ -53,7 +64,7 @@ public void TestRIPEMD160() [TestMethod] public void TestAESEncryptAndDecrypt() { - NEP6Wallet wallet = new NEP6Wallet("", "1", ProtocolSettings.Default); + NEP6Wallet wallet = new NEP6Wallet("", "1", TestProtocolSettings.Default); wallet.CreateAccount(); WalletAccount account = wallet.GetAccounts().ToArray()[0]; KeyPair key = account.GetKey(); diff --git a/tests/Neo.UnitTests/Cryptography/UT_MerkleTree.cs b/tests/Neo.UnitTests/Cryptography/UT_MerkleTree.cs index 612a013923..f8a6b19b34 100644 --- a/tests/Neo.UnitTests/Cryptography/UT_MerkleTree.cs +++ b/tests/Neo.UnitTests/Cryptography/UT_MerkleTree.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_MerkleTree.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; diff --git a/tests/Neo.UnitTests/Cryptography/UT_MerkleTreeNode.cs b/tests/Neo.UnitTests/Cryptography/UT_MerkleTreeNode.cs index 7ae6f7658b..8defb8d6c7 100644 --- a/tests/Neo.UnitTests/Cryptography/UT_MerkleTreeNode.cs +++ b/tests/Neo.UnitTests/Cryptography/UT_MerkleTreeNode.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_MerkleTreeNode.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; @@ -8,7 +19,7 @@ namespace Neo.UnitTests.Cryptography [TestClass] public class UT_MerkleTreeNode { - private MerkleTreeNode node = new MerkleTreeNode(); + private readonly MerkleTreeNode node = new MerkleTreeNode(); [TestInitialize] public void TestSetup() diff --git a/tests/Neo.UnitTests/Cryptography/UT_Murmur128.cs b/tests/Neo.UnitTests/Cryptography/UT_Murmur128.cs index 49f6657696..f33dd7110a 100644 --- a/tests/Neo.UnitTests/Cryptography/UT_Murmur128.cs +++ b/tests/Neo.UnitTests/Cryptography/UT_Murmur128.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Murmur128.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; diff --git a/tests/Neo.UnitTests/Cryptography/UT_Murmur32.cs b/tests/Neo.UnitTests/Cryptography/UT_Murmur32.cs index 1b5b2fc218..1c885dbd08 100644 --- a/tests/Neo.UnitTests/Cryptography/UT_Murmur32.cs +++ b/tests/Neo.UnitTests/Cryptography/UT_Murmur32.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Murmur32.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; diff --git a/tests/Neo.UnitTests/Cryptography/UT_SCrypt.cs b/tests/Neo.UnitTests/Cryptography/UT_SCrypt.cs index f0934b8138..c3a3127ff0 100644 --- a/tests/Neo.UnitTests/Cryptography/UT_SCrypt.cs +++ b/tests/Neo.UnitTests/Cryptography/UT_SCrypt.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_SCrypt.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Microsoft.VisualStudio.TestTools.UnitTesting; using Org.BouncyCastle.Crypto.Generators; diff --git a/tests/Neo.UnitTests/Extensions/NativeContractExtensions.cs b/tests/Neo.UnitTests/Extensions/NativeContractExtensions.cs index 437f98bbe4..9a282759a9 100644 --- a/tests/Neo.UnitTests/Extensions/NativeContractExtensions.cs +++ b/tests/Neo.UnitTests/Extensions/NativeContractExtensions.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// NativeContractExtensions.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Neo.Network.P2P.Payloads; using Neo.Persistence; using Neo.SmartContract; @@ -10,13 +21,23 @@ namespace Neo.UnitTests.Extensions { public static class NativeContractExtensions { - public static ContractState DeployContract(this DataCache snapshot, UInt160 sender, byte[] nefFile, byte[] manifest, long gas = 200_00000000) + /// + /// Deploy a contract to the blockchain. + /// + /// The snapshot used for deploying the contract. + /// The address of the contract deployer. + /// The file of the contract to be deployed. + /// The manifest of the contract to be deployed. + /// The gas fee to spend for deploying the contract in the unit of datoshi, 1 datoshi = 1e-8 GAS. + /// + /// + public static ContractState DeployContract(this DataCache snapshot, UInt160 sender, byte[] nefFile, byte[] manifest, long datoshi = 200_00000000) { var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.ContractManagement.Hash, "deploy", nefFile, manifest, null); var engine = ApplicationEngine.Create(TriggerType.Application, - sender != null ? new Transaction() { Signers = new Signer[] { new Signer() { Account = sender } }, Attributes = System.Array.Empty() } : null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: gas); + sender != null ? new Transaction() { Signers = new Signer[] { new Signer() { Account = sender } }, Attributes = System.Array.Empty() } : null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: datoshi); engine.LoadScript(script.ToArray()); if (engine.Execute() != VMState.HALT) diff --git a/tests/Neo.UnitTests/Extensions/Nep17NativeContractExtensions.cs b/tests/Neo.UnitTests/Extensions/Nep17NativeContractExtensions.cs index 89c4f66f17..a3f33f26a7 100644 --- a/tests/Neo.UnitTests/Extensions/Nep17NativeContractExtensions.cs +++ b/tests/Neo.UnitTests/Extensions/Nep17NativeContractExtensions.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Nep17NativeContractExtensions.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Neo.IO; using Neo.Network.P2P.Payloads; diff --git a/tests/Neo.UnitTests/IO/Caching/UT_Cache.cs b/tests/Neo.UnitTests/IO/Caching/UT_Cache.cs index 529ac66a9e..a622cc3767 100644 --- a/tests/Neo.UnitTests/IO/Caching/UT_Cache.cs +++ b/tests/Neo.UnitTests/IO/Caching/UT_Cache.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Cache.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO.Caching; diff --git a/tests/Neo.UnitTests/IO/Caching/UT_CloneCache.cs b/tests/Neo.UnitTests/IO/Caching/UT_CloneCache.cs index 990b21ea59..4d57da62b9 100644 --- a/tests/Neo.UnitTests/IO/Caching/UT_CloneCache.cs +++ b/tests/Neo.UnitTests/IO/Caching/UT_CloneCache.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_CloneCache.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; diff --git a/tests/Neo.UnitTests/IO/Caching/UT_DataCache.cs b/tests/Neo.UnitTests/IO/Caching/UT_DataCache.cs index 32d6fcd408..5235b99c70 100644 --- a/tests/Neo.UnitTests/IO/Caching/UT_DataCache.cs +++ b/tests/Neo.UnitTests/IO/Caching/UT_DataCache.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_DataCache.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; @@ -13,7 +24,7 @@ namespace Neo.UnitTests.IO.Caching [TestClass] public class UT_DataCache { - private MemoryStore store = new(); + private readonly MemoryStore store = new(); private SnapshotCache myDataCache; private static readonly StorageKey key1 = new() { Id = 0, Key = Encoding.UTF8.GetBytes("key1") }; diff --git a/tests/Neo.UnitTests/IO/Caching/UT_ECPointCache.cs b/tests/Neo.UnitTests/IO/Caching/UT_ECPointCache.cs index dc36bdb96f..87be485ab6 100644 --- a/tests/Neo.UnitTests/IO/Caching/UT_ECPointCache.cs +++ b/tests/Neo.UnitTests/IO/Caching/UT_ECPointCache.cs @@ -1,9 +1,18 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ECPointCache.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; using Neo.IO.Caching; -using Neo.Network.P2P.Payloads; -using System; namespace Neo.UnitTests.IO.Caching { diff --git a/tests/Neo.UnitTests/IO/Caching/UT_HashSetCache.cs b/tests/Neo.UnitTests/IO/Caching/UT_HashSetCache.cs index ceb51130d2..9357aeb418 100644 --- a/tests/Neo.UnitTests/IO/Caching/UT_HashSetCache.cs +++ b/tests/Neo.UnitTests/IO/Caching/UT_HashSetCache.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_HashSetCache.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO.Caching; diff --git a/tests/Neo.UnitTests/IO/Caching/UT_IndexedQueue.cs b/tests/Neo.UnitTests/IO/Caching/UT_IndexedQueue.cs index e09726924c..4c7cc0697e 100644 --- a/tests/Neo.UnitTests/IO/Caching/UT_IndexedQueue.cs +++ b/tests/Neo.UnitTests/IO/Caching/UT_IndexedQueue.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_IndexedQueue.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO.Caching; diff --git a/tests/Neo.UnitTests/IO/Caching/UT_ReflectionCache.cs b/tests/Neo.UnitTests/IO/Caching/UT_ReflectionCache.cs index 726b751946..57e466d837 100644 --- a/tests/Neo.UnitTests/IO/Caching/UT_ReflectionCache.cs +++ b/tests/Neo.UnitTests/IO/Caching/UT_ReflectionCache.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ReflectionCache.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; diff --git a/tests/Neo.UnitTests/IO/Caching/UT_RelayCache.cs b/tests/Neo.UnitTests/IO/Caching/UT_RelayCache.cs index 659b7f5797..a6c5ea3939 100644 --- a/tests/Neo.UnitTests/IO/Caching/UT_RelayCache.cs +++ b/tests/Neo.UnitTests/IO/Caching/UT_RelayCache.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_RelayCache.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO.Caching; diff --git a/tests/Neo.UnitTests/IO/UT_ByteArrayComparer.cs b/tests/Neo.UnitTests/IO/UT_ByteArrayComparer.cs index 89c0c621d7..19c3fafcd6 100644 --- a/tests/Neo.UnitTests/IO/UT_ByteArrayComparer.cs +++ b/tests/Neo.UnitTests/IO/UT_ByteArrayComparer.cs @@ -1,6 +1,18 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ByteArrayComparer.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; +using System; namespace Neo.UnitTests.IO { @@ -11,10 +23,23 @@ public class UT_ByteArrayComparer public void TestCompare() { ByteArrayComparer comparer = ByteArrayComparer.Default; - byte[] x = new byte[0], y = new byte[0]; + byte[] x = null, y = null; + comparer.Compare(x, y).Should().Be(0); + + x = new byte[] { 1, 2, 3, 4, 5 }; + y = x; comparer.Compare(x, y).Should().Be(0); + comparer.Compare(x, x).Should().Be(0); + + y = null; + comparer.Compare(x, y).Should().Be(5); + + y = x; + x = null; + comparer.Compare(x, y).Should().Be(-5); x = new byte[] { 1 }; + y = Array.Empty(); comparer.Compare(x, y).Should().Be(1); y = x; comparer.Compare(x, y).Should().Be(0); diff --git a/tests/Neo.UnitTests/IO/UT_ByteArrayEqualityComparer.cs b/tests/Neo.UnitTests/IO/UT_ByteArrayEqualityComparer.cs index 6b7152c239..ea5ae86be4 100644 --- a/tests/Neo.UnitTests/IO/UT_ByteArrayEqualityComparer.cs +++ b/tests/Neo.UnitTests/IO/UT_ByteArrayEqualityComparer.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ByteArrayEqualityComparer.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; using System; diff --git a/tests/Neo.UnitTests/IO/UT_IOHelper.cs b/tests/Neo.UnitTests/IO/UT_IOHelper.cs index 80fd3c3105..4b74a495b5 100644 --- a/tests/Neo.UnitTests/IO/UT_IOHelper.cs +++ b/tests/Neo.UnitTests/IO/UT_IOHelper.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_IOHelper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; diff --git a/tests/Neo.UnitTests/IO/UT_MemoryReader.cs b/tests/Neo.UnitTests/IO/UT_MemoryReader.cs index 065bb37e51..a045a0b688 100644 --- a/tests/Neo.UnitTests/IO/UT_MemoryReader.cs +++ b/tests/Neo.UnitTests/IO/UT_MemoryReader.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_MemoryReader.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; using System.IO; diff --git a/tests/Neo.UnitTests/Ledger/UT_Blockchain.cs b/tests/Neo.UnitTests/Ledger/UT_Blockchain.cs index 1cd8c42360..491b4fe21e 100644 --- a/tests/Neo.UnitTests/Ledger/UT_Blockchain.cs +++ b/tests/Neo.UnitTests/Ledger/UT_Blockchain.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Blockchain.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Akka.TestKit; using Akka.TestKit.Xunit2; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -37,6 +48,12 @@ public void Initialize() system.MemPool.TryAdd(txSample, TestBlockchain.GetTestSnapshot()); } + [TestCleanup] + public void Clean() + { + TestBlockchain.ResetStore(); + } + [TestMethod] public void TestValidTransaction() { @@ -59,7 +76,7 @@ public void TestValidTransaction() senderProbe.ExpectMsg(p => p.Result == VerifyResult.Succeed); senderProbe.Send(system.Blockchain, tx); - senderProbe.ExpectMsg(p => p.Result == VerifyResult.AlreadyExists); + senderProbe.ExpectMsg(p => p.Result == VerifyResult.AlreadyInPool); } internal static StorageKey CreateStorageKey(byte prefix, byte[] key = null) @@ -89,7 +106,7 @@ private static Transaction CreateValidTx(DataCache snapshot, NEP6Wallet wallet, tx.Nonce = nonce; - var data = new ContractParametersContext(snapshot, tx, ProtocolSettings.Default.Network); + var data = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); Assert.IsNull(data.GetSignatures(tx.Sender)); Assert.IsTrue(wallet.Sign(data)); Assert.IsTrue(data.Completed); @@ -135,7 +152,7 @@ public void TestMaliciousOnChainConflict() { Header = new Header() { - Index = 10000, + Index = 5, // allow tx1, tx2 and tx3 to fit into MaxValidUntilBlockIncrement. MerkleRoot = UInt256.Zero, NextConsensus = UInt160.Zero, PrevHash = UInt256.Zero, @@ -149,13 +166,26 @@ public void TestMaliciousOnChainConflict() sb.EmitSysCall(ApplicationEngine.System_Contract_NativeOnPersist); onPersistScript = sb.ToArray(); } - TransactionState[] transactionStates; using (ApplicationEngine engine2 = ApplicationEngine.Create(TriggerType.OnPersist, null, snapshot, block, TestBlockchain.TheNeoSystem.Settings, 0)) { engine2.LoadScript(onPersistScript); - if (engine2.Execute() != VMState.HALT) throw new InvalidOperationException(); - Blockchain.ApplicationExecuted application_executed = new(engine2); - transactionStates = engine2.GetState(); + if (engine2.Execute() != VMState.HALT) throw engine2.FaultException; + engine2.Snapshot.Commit(); + } + snapshot.Commit(); + + // Run PostPersist to update current block index in native Ledger. + // Relevant current block index is needed for conflict records checks. + byte[] postPersistScript; + using (ScriptBuilder sb = new()) + { + sb.EmitSysCall(ApplicationEngine.System_Contract_NativePostPersist); + postPersistScript = sb.ToArray(); + } + using (ApplicationEngine engine2 = ApplicationEngine.Create(TriggerType.PostPersist, null, snapshot, block, TestBlockchain.TheNeoSystem.Settings, 0)) + { + engine2.LoadScript(postPersistScript); + if (engine2.Execute() != VMState.HALT) throw engine2.FaultException; engine2.Snapshot.Commit(); } snapshot.Commit(); diff --git a/tests/Neo.UnitTests/Ledger/UT_HashIndexState.cs b/tests/Neo.UnitTests/Ledger/UT_HashIndexState.cs index 7cebe1d9bf..5351f17268 100644 --- a/tests/Neo.UnitTests/Ledger/UT_HashIndexState.cs +++ b/tests/Neo.UnitTests/Ledger/UT_HashIndexState.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_HashIndexState.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; @@ -25,10 +36,10 @@ public void Initialize() [TestMethod] public void TestDeserialize() { - var data = BinarySerializer.Serialize(((IInteroperable)origin).ToStackItem(null), 1024); + var data = BinarySerializer.Serialize(((IInteroperable)origin).ToStackItem(null), ExecutionEngineLimits.Default); var reader = new MemoryReader(data); - HashIndexState dest = new HashIndexState(); + HashIndexState dest = new(); ((IInteroperable)dest).FromStackItem(BinarySerializer.Deserialize(ref reader, ExecutionEngineLimits.Default, null)); dest.Hash.Should().Be(origin.Hash); diff --git a/tests/Neo.UnitTests/Ledger/UT_MemoryPool.cs b/tests/Neo.UnitTests/Ledger/UT_MemoryPool.cs index de5bff6ba0..1063413073 100644 --- a/tests/Neo.UnitTests/Ledger/UT_MemoryPool.cs +++ b/tests/Neo.UnitTests/Ledger/UT_MemoryPool.cs @@ -1,9 +1,14 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using System.Threading.Tasks; +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_MemoryPool.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Akka.TestKit.Xunit2; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -15,7 +20,12 @@ using Neo.Persistence; using Neo.SmartContract; using Neo.SmartContract.Native; -using Neo.VM; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Threading.Tasks; namespace Neo.UnitTests.Ledger { @@ -47,7 +57,7 @@ public void TestSetup() TimeProvider.ResetToDefault(); // Create a MemoryPool with capacity of 100 - _unit = new MemoryPool(new NeoSystem(ProtocolSettings.Default with { MemoryPoolMaxTransactions = 100 })); + _unit = new MemoryPool(new NeoSystem(TestProtocolSettings.Default with { MemoryPoolMaxTransactions = 100 }, storageProvider: (string)null)); // Verify capacity equals the amount specified _unit.Capacity.Should().Be(100); @@ -638,7 +648,7 @@ public void TestGetVerifiedTransactions() [TestMethod] public void TestReVerifyTopUnverifiedTransactionsIfNeeded() { - _unit = new MemoryPool(new NeoSystem(ProtocolSettings.Default with { MemoryPoolMaxTransactions = 600 })); + _unit = new MemoryPool(new NeoSystem(TestProtocolSettings.Default with { MemoryPoolMaxTransactions = 600 }, storageProvider: (string)null)); AddTransaction(CreateTransaction(100000001)); AddTransaction(CreateTransaction(100000001)); @@ -736,74 +746,6 @@ public void TestUpdatePoolForBlockPersisted() _unit.VerifiedCount.Should().Be(0); } - - [TestMethod] - public async Task Malicious_OnChain_Conflict() - { - // Arrange: prepare mempooled txs that have conflicts. - long txFee = 1; - var snapshot = GetSnapshot(); - BigInteger balance = NativeContract.GAS.BalanceOf(snapshot, senderAccount); - ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: long.MaxValue); - engine.LoadScript(Array.Empty()); - - var tx1 = CreateTransactionWithFeeAndBalanceVerify(txFee); - var tx2 = CreateTransactionWithFeeAndBalanceVerify(txFee); - - tx1.Signers[0].Account = UInt160.Parse("0x0001020304050607080900010203040506070809"); // Different sender - - await NativeContract.GAS.Mint(engine, tx1.Sender, 100000, true); // balance enough for all mempooled txs - await NativeContract.GAS.Mint(engine, tx2.Sender, 100000, true); // balance enough for all mempooled txs - - tx1.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = tx2.Hash } }; - - Assert.AreEqual(_unit.TryAdd(tx1, engine.Snapshot), VerifyResult.Succeed); - - var block = new Block - { - Header = new Header() - { - Index = 10000, - MerkleRoot = UInt256.Zero, - NextConsensus = UInt160.Zero, - PrevHash = UInt256.Zero, - Witness = new Witness() { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() } - }, - Transactions = new Transaction[] { tx1 }, - }; - _unit.UpdatePoolForBlockPersisted(block, engine.Snapshot); - - _unit.SortedTxCount.Should().Be(0); - - // Simulate persist tx1 - - Assert.AreEqual(_unit.TryAdd(tx2, engine.Snapshot), VerifyResult.Succeed); - - byte[] onPersistScript; - using (ScriptBuilder sb = new()) - { - sb.EmitSysCall(ApplicationEngine.System_Contract_NativeOnPersist); - onPersistScript = sb.ToArray(); - } - - TransactionState[] transactionStates; - using (ApplicationEngine engine2 = ApplicationEngine.Create(TriggerType.OnPersist, null, engine.Snapshot, block, TestBlockchain.TheNeoSystem.Settings, 0)) - { - engine2.LoadScript(onPersistScript); - if (engine2.Execute() != VMState.HALT) throw new InvalidOperationException(); - Blockchain.ApplicationExecuted application_executed = new(engine2); - transactionStates = engine2.GetState(); - } - - // Test tx2 arrive - - snapshot.Commit(); - - var senderProbe = CreateTestProbe(); - senderProbe.Send(TestBlockchain.TheNeoSystem.Blockchain, tx2); - senderProbe.ExpectMsg(p => p.Result == VerifyResult.InsufficientFunds); // should be Succedded. - } - public static StorageKey CreateStorageKey(int id, byte prefix, byte[] key = null) { byte[] buffer = GC.AllocateUninitializedArray(sizeof(byte) + (key?.Length ?? 0)); diff --git a/tests/Neo.UnitTests/Ledger/UT_PoolItem.cs b/tests/Neo.UnitTests/Ledger/UT_PoolItem.cs index 6933bfb292..2b8e54b919 100644 --- a/tests/Neo.UnitTests/Ledger/UT_PoolItem.cs +++ b/tests/Neo.UnitTests/Ledger/UT_PoolItem.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_PoolItem.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -36,11 +47,11 @@ public void TestCleanup() public void PoolItem_CompareTo_Fee() { int size1 = 51; - int netFeeSatoshi1 = 1; - var tx1 = GenerateTx(netFeeSatoshi1, size1); + int netFeeDatoshi1 = 1; + var tx1 = GenerateTx(netFeeDatoshi1, size1); int size2 = 51; - int netFeeSatoshi2 = 2; - var tx2 = GenerateTx(netFeeSatoshi2, size2); + int netFeeDatoshi2 = 2; + var tx2 = GenerateTx(netFeeDatoshi2, size2); PoolItem pitem1 = new PoolItem(tx1); PoolItem pitem2 = new PoolItem(tx2); @@ -56,10 +67,10 @@ public void PoolItem_CompareTo_Fee() public void PoolItem_CompareTo_Hash() { int sizeFixed = 51; - int netFeeSatoshiFixed = 1; + int netFeeDatoshiFixed = 1; - var tx1 = GenerateTxWithFirstByteOfHashGreaterThanOrEqualTo(0x80, netFeeSatoshiFixed, sizeFixed); - var tx2 = GenerateTxWithFirstByteOfHashLessThanOrEqualTo(0x79, netFeeSatoshiFixed, sizeFixed); + var tx1 = GenerateTxWithFirstByteOfHashGreaterThanOrEqualTo(0x80, netFeeDatoshiFixed, sizeFixed); + var tx2 = GenerateTxWithFirstByteOfHashLessThanOrEqualTo(0x79, netFeeDatoshiFixed, sizeFixed); tx1.Attributes = new TransactionAttribute[] { new HighPriorityAttribute() }; @@ -72,8 +83,8 @@ public void PoolItem_CompareTo_Hash() // Bulk test for (int testRuns = 0; testRuns < 30; testRuns++) { - tx1 = GenerateTxWithFirstByteOfHashGreaterThanOrEqualTo(0x80, netFeeSatoshiFixed, sizeFixed); - tx2 = GenerateTxWithFirstByteOfHashLessThanOrEqualTo(0x79, netFeeSatoshiFixed, sizeFixed); + tx1 = GenerateTxWithFirstByteOfHashGreaterThanOrEqualTo(0x80, netFeeDatoshiFixed, sizeFixed); + tx2 = GenerateTxWithFirstByteOfHashLessThanOrEqualTo(0x79, netFeeDatoshiFixed, sizeFixed); pitem1 = new PoolItem(tx1); pitem2 = new PoolItem(tx2); @@ -92,8 +103,8 @@ public void PoolItem_CompareTo_Hash() public void PoolItem_CompareTo_Equals() { int sizeFixed = 500; - int netFeeSatoshiFixed = 10; - var tx = GenerateTx(netFeeSatoshiFixed, sizeFixed, new byte[] { 0x13, 0x37 }); + int netFeeDatoshiFixed = 10; + var tx = GenerateTx(netFeeDatoshiFixed, sizeFixed, new byte[] { 0x13, 0x37 }); PoolItem pitem1 = new PoolItem(tx); PoolItem pitem2 = new PoolItem(tx); diff --git a/tests/Neo.UnitTests/Ledger/UT_StorageItem.cs b/tests/Neo.UnitTests/Ledger/UT_StorageItem.cs index 7140023889..8ee55919da 100644 --- a/tests/Neo.UnitTests/Ledger/UT_StorageItem.cs +++ b/tests/Neo.UnitTests/Ledger/UT_StorageItem.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_StorageItem.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; diff --git a/tests/Neo.UnitTests/Ledger/UT_StorageKey.cs b/tests/Neo.UnitTests/Ledger/UT_StorageKey.cs index b37ab78061..f2d9300fee 100644 --- a/tests/Neo.UnitTests/Ledger/UT_StorageKey.cs +++ b/tests/Neo.UnitTests/Ledger/UT_StorageKey.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_StorageKey.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.SmartContract; diff --git a/tests/Neo.UnitTests/Ledger/UT_TransactionState.cs b/tests/Neo.UnitTests/Ledger/UT_TransactionState.cs index 0270d97891..0a5f1a102d 100644 --- a/tests/Neo.UnitTests/Ledger/UT_TransactionState.cs +++ b/tests/Neo.UnitTests/Ledger/UT_TransactionState.cs @@ -1,6 +1,16 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_TransactionState.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Cryptography; using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.SmartContract; @@ -36,18 +46,14 @@ public void Initialize() }; originTrimmed = new TransactionState { - ConflictingSigners = new UInt160[] - { - new UInt160(Crypto.Hash160(new byte[] { 1, 2, 3 })), - new UInt160(Crypto.Hash160(new byte[] { 4, 5, 6 })) - } + BlockIndex = 1, }; } [TestMethod] public void TestDeserialize() { - var data = BinarySerializer.Serialize(((IInteroperable)origin).ToStackItem(null), 1024); + var data = BinarySerializer.Serialize(((IInteroperable)origin).ToStackItem(null), ExecutionEngineLimits.Default); var reader = new MemoryReader(data); TransactionState dest = new(); @@ -61,16 +67,15 @@ public void TestDeserialize() [TestMethod] public void TestDeserializeTrimmed() { - var data = BinarySerializer.Serialize(((IInteroperable)originTrimmed).ToStackItem(null), 1024); + var data = BinarySerializer.Serialize(((IInteroperable)originTrimmed).ToStackItem(null), ExecutionEngineLimits.Default); var reader = new MemoryReader(data); TransactionState dest = new(); ((IInteroperable)dest).FromStackItem(BinarySerializer.Deserialize(ref reader, ExecutionEngineLimits.Default, null)); - dest.BlockIndex.Should().Be(0); + dest.BlockIndex.Should().Be(originTrimmed.BlockIndex); dest.Transaction.Should().Be(null); dest.Transaction.Should().BeNull(); - CollectionAssert.AreEqual(originTrimmed.ConflictingSigners, dest.ConflictingSigners); } } } diff --git a/tests/Neo.UnitTests/Ledger/UT_TransactionVerificationContext.cs b/tests/Neo.UnitTests/Ledger/UT_TransactionVerificationContext.cs index 522b964ff9..32a0bd6237 100644 --- a/tests/Neo.UnitTests/Ledger/UT_TransactionVerificationContext.cs +++ b/tests/Neo.UnitTests/Ledger/UT_TransactionVerificationContext.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_TransactionVerificationContext.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; diff --git a/tests/Neo.UnitTests/Ledger/UT_TrimmedBlock.cs b/tests/Neo.UnitTests/Ledger/UT_TrimmedBlock.cs index 5a62071cad..7942847f94 100644 --- a/tests/Neo.UnitTests/Ledger/UT_TrimmedBlock.cs +++ b/tests/Neo.UnitTests/Ledger/UT_TrimmedBlock.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_TrimmedBlock.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; @@ -105,7 +116,7 @@ public void TestDeserialize() newBlock.Deserialize(ref reader); } tblock.Hashes.Length.Should().Be(newBlock.Hashes.Length); - tblock.Header.ToJson(ProtocolSettings.Default).ToString().Should().Be(newBlock.Header.ToJson(ProtocolSettings.Default).ToString()); + tblock.Header.ToJson(TestProtocolSettings.Default).ToString().Should().Be(newBlock.Header.ToJson(ProtocolSettings.Default).ToString()); } } } diff --git a/tests/Neo.UnitTests/Neo.UnitTests.csproj b/tests/Neo.UnitTests/Neo.UnitTests.csproj index 1aef941090..fb246cbdc3 100644 --- a/tests/Neo.UnitTests/Neo.UnitTests.csproj +++ b/tests/Neo.UnitTests/Neo.UnitTests.csproj @@ -1,13 +1,21 @@ - + + net8.0 true - - - + + + + + + + + PreserveNewest + PreserveNewest + @@ -15,6 +23,9 @@ - + + + + diff --git a/tests/Neo.UnitTests/Network/P2P/Capabilities/UT_FullNodeCapability.cs b/tests/Neo.UnitTests/Network/P2P/Capabilities/UT_FullNodeCapability.cs index f03134bd3e..c3c5c2c86f 100644 --- a/tests/Neo.UnitTests/Network/P2P/Capabilities/UT_FullNodeCapability.cs +++ b/tests/Neo.UnitTests/Network/P2P/Capabilities/UT_FullNodeCapability.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_FullNodeCapability.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; diff --git a/tests/Neo.UnitTests/Network/P2P/Capabilities/UT_ServerCapability.cs b/tests/Neo.UnitTests/Network/P2P/Capabilities/UT_ServerCapability.cs index 8e8d6dd8c0..b113d59da7 100644 --- a/tests/Neo.UnitTests/Network/P2P/Capabilities/UT_ServerCapability.cs +++ b/tests/Neo.UnitTests/Network/P2P/Capabilities/UT_ServerCapability.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ServerCapability.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; @@ -15,13 +26,16 @@ public void Size_Get() var test = new ServerCapability(NodeCapabilityType.TcpServer) { Port = 1 }; test.Size.Should().Be(3); +#pragma warning disable CS0612 // Type or member is obsolete test = new ServerCapability(NodeCapabilityType.WsServer) { Port = 2 }; +#pragma warning restore CS0612 // Type or member is obsolete test.Size.Should().Be(3); } [TestMethod] public void DeserializeAndSerialize() { +#pragma warning disable CS0612 // Type or member is obsolete var test = new ServerCapability(NodeCapabilityType.WsServer) { Port = 2 }; var buffer = test.ToArray(); @@ -32,6 +46,7 @@ public void DeserializeAndSerialize() Assert.AreEqual(test.Type, clone.Type); clone = new ServerCapability(NodeCapabilityType.WsServer, 123); +#pragma warning restore CS0612 // Type or member is obsolete br = new MemoryReader(buffer); ((ISerializable)clone).Deserialize(ref br); @@ -50,7 +65,7 @@ public void DeserializeAndSerialize() _ = new ServerCapability(NodeCapabilityType.FullNode); }); - // Wrog type + // Wrong type buffer[0] = 0xFF; Assert.ThrowsException(() => { diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_AddrPayload.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_AddrPayload.cs index fa19860ff2..6e189ad3ec 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_AddrPayload.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_AddrPayload.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_AddrPayload.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Block.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Block.cs index d839af965b..2021e5a187 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Block.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Block.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Block.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; @@ -59,7 +70,7 @@ public void Size_Get_1_Transaction() TestUtils.GetTransaction(UInt160.Zero) }; - uut.Size.Should().Be(167); // 159 + nonce + uut.Size.Should().Be(167); // 159 + nonce } [TestMethod] @@ -162,7 +173,7 @@ public void ToJson() UInt256 val256 = UInt256.Zero; TestUtils.SetupBlockWithValues(uut, val256, out _, out _, out var timeVal, out var indexVal, out var nonceVal, out _, out _, 1); - JObject jObj = uut.ToJson(ProtocolSettings.Default); + JObject jObj = uut.ToJson(TestProtocolSettings.Default); jObj.Should().NotBeNull(); jObj["hash"].AsString().Should().Be("0x60193a05005c433787d8a9b95da332bbeebb311e904525e9fb1bacc34ff1ead7"); jObj["size"].AsNumber().Should().Be(167); // 159 + nonce diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Conflicts.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Conflicts.cs index bbf46920aa..ce46c5106d 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Conflicts.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Conflicts.cs @@ -1,9 +1,20 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Conflicts.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; using Neo.Network.P2P.Payloads; -using Neo.SmartContract.Native; using Neo.SmartContract; +using Neo.SmartContract.Native; using Neo.VM; using System; @@ -13,7 +24,7 @@ namespace Neo.UnitTests.Network.P2P.Payloads public class UT_Conflicts { private const byte Prefix_Transaction = 11; - private static UInt256 _u = new UInt256(new byte[32] { + private static readonly UInt256 _u = new UInt256(new byte[32] { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_ExtensiblePayload.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_ExtensiblePayload.cs index 77663bda41..1a0ad19a9a 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_ExtensiblePayload.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_ExtensiblePayload.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ExtensiblePayload.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_FilterAddPayload.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_FilterAddPayload.cs index d0553fcc5a..76eff8e198 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_FilterAddPayload.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_FilterAddPayload.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_FilterAddPayload.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_FilterLoadPayload.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_FilterLoadPayload.cs index aa832fe427..f73e9b5595 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_FilterLoadPayload.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_FilterLoadPayload.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_FilterLoadPayload.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_GetBlockByIndexPayload.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_GetBlockByIndexPayload.cs index 76ed1df9f9..3aa4049d87 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_GetBlockByIndexPayload.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_GetBlockByIndexPayload.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_GetBlockByIndexPayload.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_GetBlocksPayload.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_GetBlocksPayload.cs index 4c09147d8d..6752525ac0 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_GetBlocksPayload.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_GetBlocksPayload.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_GetBlocksPayload.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Header.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Header.cs index 8bf065eef8..df33505965 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Header.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Header.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Header.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_HeadersPayload.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_HeadersPayload.cs index b099403429..2ac486ef06 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_HeadersPayload.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_HeadersPayload.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_HeadersPayload.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_HighPriorityAttribute.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_HighPriorityAttribute.cs index 8a41aa0135..76808072d5 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_HighPriorityAttribute.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_HighPriorityAttribute.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_HighPriorityAttribute.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_InvPayload.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_InvPayload.cs index 9bd5127af9..4735fddfe1 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_InvPayload.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_InvPayload.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_InvPayload.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_MerkleBlockPayload.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_MerkleBlockPayload.cs index ac658954e3..f491d46749 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_MerkleBlockPayload.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_MerkleBlockPayload.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_MerkleBlockPayload.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NetworkAddressWithTime.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NetworkAddressWithTime.cs index 239896acf8..2ed86e4da7 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NetworkAddressWithTime.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NetworkAddressWithTime.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_NetworkAddressWithTime.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NotValidBefore.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NotValidBefore.cs index e47159d9ff..8e7ad9087b 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NotValidBefore.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NotValidBefore.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_NotValidBefore.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; @@ -20,8 +31,10 @@ public void Size_Get() [TestMethod] public void ToJson() { - var test = new NotValidBefore(); - test.Height = 42; + var test = new NotValidBefore + { + Height = 42 + }; var json = test.ToJson().ToString(); Assert.AreEqual(@"{""type"":""NotValidBefore"",""height"":42}", json); } diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Signers.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Signers.cs index 8b1adae4ee..44d337fd1c 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Signers.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Signers.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Signers.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Transaction.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Transaction.cs index 1df1340012..56d9a66bec 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Transaction.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Transaction.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Transaction.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; @@ -27,6 +38,12 @@ public void TestSetup() uut = new Transaction(); } + [TestCleanup] + public void Clean() + { + TestBlockchain.ResetStore(); + } + [TestMethod] public void Script_Get() { @@ -145,10 +162,10 @@ public void FeeIsMultiSigContract() // Sign - var wrongData = new ContractParametersContext(snapshot, tx, ProtocolSettings.Default.Network + 1); + var wrongData = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network + 1); Assert.IsFalse(walletA.Sign(wrongData)); - var data = new ContractParametersContext(snapshot, tx, ProtocolSettings.Default.Network); + var data = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); Assert.IsTrue(walletA.Sign(data)); Assert.IsTrue(walletB.Sign(data)); Assert.IsTrue(data.Completed); @@ -157,7 +174,7 @@ public void FeeIsMultiSigContract() // Fast check - Assert.IsTrue(tx.VerifyWitnesses(ProtocolSettings.Default, snapshot, tx.NetworkFee)); + Assert.IsTrue(tx.VerifyWitnesses(TestProtocolSettings.Default, snapshot, tx.NetworkFee)); // Check @@ -170,7 +187,7 @@ public void FeeIsMultiSigContract() Assert.AreEqual(VMState.HALT, engine.Execute()); Assert.AreEqual(1, engine.ResultStack.Count); Assert.IsTrue(engine.ResultStack.Pop().GetBoolean()); - verificationGas += engine.GasConsumed; + verificationGas += engine.FeeConsumed; } var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); @@ -219,7 +236,7 @@ public void FeeIsSignatureContractDetailed() // Sign // ---- - var data = new ContractParametersContext(snapshot, tx, ProtocolSettings.Default.Network); + var data = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); // 'from' is always required as witness // if not included on cosigner with a scope, its scope should be considered 'CalledByEntry' data.ScriptHashes.Count.Should().Be(1); @@ -233,7 +250,7 @@ public void FeeIsSignatureContractDetailed() // Fast check - Assert.IsTrue(tx.VerifyWitnesses(ProtocolSettings.Default, snapshot, tx.NetworkFee)); + Assert.IsTrue(tx.VerifyWitnesses(TestProtocolSettings.Default, snapshot, tx.NetworkFee)); // Check @@ -246,7 +263,7 @@ public void FeeIsSignatureContractDetailed() Assert.AreEqual(VMState.HALT, engine.Execute()); Assert.AreEqual(1, engine.ResultStack.Count); Assert.IsTrue(engine.ResultStack.Pop().GetBoolean()); - verificationGas += engine.GasConsumed; + verificationGas += engine.FeeConsumed; } // ------------------ @@ -330,7 +347,7 @@ public void FeeIsSignatureContract_TestScope_Global() // Sign // ---- - var data = new ContractParametersContext(snapshot, tx, ProtocolSettings.Default.Network); + var data = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); bool signed = wallet.Sign(data); Assert.IsTrue(signed); @@ -339,7 +356,7 @@ public void FeeIsSignatureContract_TestScope_Global() tx.Witnesses.Length.Should().Be(1); // Fast check - Assert.IsTrue(tx.VerifyWitnesses(ProtocolSettings.Default, snapshot, tx.NetworkFee)); + Assert.IsTrue(tx.VerifyWitnesses(TestProtocolSettings.Default, snapshot, tx.NetworkFee)); // Check long verificationGas = 0; @@ -351,7 +368,7 @@ public void FeeIsSignatureContract_TestScope_Global() Assert.AreEqual(VMState.HALT, engine.Execute()); Assert.AreEqual(1, engine.ResultStack.Count); Assert.IsTrue(engine.ResultStack.Pop().GetBoolean()); - verificationGas += engine.GasConsumed; + verificationGas += engine.FeeConsumed; } // get sizeGas var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); @@ -410,7 +427,7 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_GAS() // Sign // ---- - var data = new ContractParametersContext(snapshot, tx, ProtocolSettings.Default.Network); + var data = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); bool signed = wallet.Sign(data); Assert.IsTrue(signed); @@ -419,7 +436,7 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_GAS() tx.Witnesses.Length.Should().Be(1); // Fast check - Assert.IsTrue(tx.VerifyWitnesses(ProtocolSettings.Default, snapshot, tx.NetworkFee)); + Assert.IsTrue(tx.VerifyWitnesses(TestProtocolSettings.Default, snapshot, tx.NetworkFee)); // Check long verificationGas = 0; @@ -431,7 +448,7 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_GAS() Assert.AreEqual(VMState.HALT, engine.Execute()); Assert.AreEqual(1, engine.ResultStack.Count); Assert.IsTrue(engine.ResultStack.Pop().GetBoolean()); - verificationGas += engine.GasConsumed; + verificationGas += engine.FeeConsumed; } // get sizeGas var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); @@ -493,7 +510,7 @@ public void FeeIsSignatureContract_TestScope_CalledByEntry_Plus_GAS() // Sign // ---- - var data = new ContractParametersContext(snapshot, tx, ProtocolSettings.Default.Network); + var data = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); bool signed = wallet.Sign(data); Assert.IsTrue(signed); @@ -502,7 +519,7 @@ public void FeeIsSignatureContract_TestScope_CalledByEntry_Plus_GAS() tx.Witnesses.Length.Should().Be(1); // Fast check - Assert.IsTrue(tx.VerifyWitnesses(ProtocolSettings.Default, snapshot, tx.NetworkFee)); + Assert.IsTrue(tx.VerifyWitnesses(TestProtocolSettings.Default, snapshot, tx.NetworkFee)); // Check long verificationGas = 0; @@ -514,7 +531,7 @@ public void FeeIsSignatureContract_TestScope_CalledByEntry_Plus_GAS() Assert.AreEqual(VMState.HALT, engine.Execute()); Assert.AreEqual(1, engine.ResultStack.Count); Assert.IsTrue(engine.ResultStack.Pop().GetBoolean()); - verificationGas += engine.GasConsumed; + verificationGas += engine.FeeConsumed; } // get sizeGas var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); @@ -618,7 +635,7 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_NEO_GAS() // Sign // ---- - var data = new ContractParametersContext(snapshot, tx, ProtocolSettings.Default.Network); + var data = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); bool signed = wallet.Sign(data); Assert.IsTrue(signed); @@ -632,7 +649,7 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_NEO_GAS() tx.Signers.Length.Should().Be(1); // Fast check - Assert.IsTrue(tx.VerifyWitnesses(ProtocolSettings.Default, snapshot, tx.NetworkFee)); + Assert.IsTrue(tx.VerifyWitnesses(TestProtocolSettings.Default, snapshot, tx.NetworkFee)); // Check long verificationGas = 0; @@ -644,7 +661,7 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_NEO_GAS() Assert.AreEqual(VMState.HALT, engine.Execute()); Assert.AreEqual(1, engine.ResultStack.Count); Assert.IsTrue(engine.ResultStack.Pop().GetBoolean()); - verificationGas += engine.GasConsumed; + verificationGas += engine.FeeConsumed; } // get sizeGas var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); @@ -758,7 +775,7 @@ public void Transaction_Reverify_Hashes_Length_Unequal_To_Witnesses_Length() { Version = 0x00, Nonce = 0x01020304, - SystemFee = (long)BigInteger.Pow(10, 8), // 1 GAS + SystemFee = (long)BigInteger.Pow(10, 8), // 1 GAS NetworkFee = 0x0000000000000001, ValidUntilBlock = 0x01020304, Attributes = Array.Empty(), @@ -774,7 +791,7 @@ public void Transaction_Reverify_Hashes_Length_Unequal_To_Witnesses_Length() }; UInt160[] hashes = txSimple.GetScriptHashesForVerifying(snapshot); Assert.AreEqual(1, hashes.Length); - Assert.AreNotEqual(VerifyResult.Succeed, txSimple.VerifyStateDependent(ProtocolSettings.Default, snapshot, new TransactionVerificationContext(), new List())); + Assert.AreNotEqual(VerifyResult.Succeed, txSimple.VerifyStateDependent(TestProtocolSettings.Default, snapshot, new TransactionVerificationContext(), new List())); } [TestMethod] @@ -785,7 +802,7 @@ public void Transaction_Serialize_Deserialize_Simple() { Version = 0x00, Nonce = 0x01020304, - SystemFee = (long)BigInteger.Pow(10, 8), // 1 GAS + SystemFee = (long)BigInteger.Pow(10, 8), // 1 GAS NetworkFee = 0x0000000000000001, ValidUntilBlock = 0x01020304, Signers = new Signer[] { new Signer() { Account = UInt160.Zero } }, @@ -801,8 +818,8 @@ public void Transaction_Serialize_Deserialize_Simple() "00" + // version "04030201" + // nonce "00e1f50500000000" + // system fee (1 GAS) - "0100000000000000" + // network fee (1 satoshi) - "04030201" + // timelimit + "0100000000000000" + // network fee (1 datoshi) + "04030201" + // timelimit "01000000000000000000000000000000000000000000" + // empty signer "00" + // no attributes "0111" + // push1 script @@ -842,7 +859,7 @@ public void Transaction_Serialize_Deserialize_DistinctCosigners() { Version = 0x00, Nonce = 0x01020304, - SystemFee = (long)BigInteger.Pow(10, 8), // 1 GAS + SystemFee = (long)BigInteger.Pow(10, 8), // 1 GAS NetworkFee = 0x0000000000000001, ValidUntilBlock = 0x01020304, Attributes = Array.Empty(), @@ -904,7 +921,7 @@ public void Transaction_Serialize_Deserialize_MaxSizeCosigners() { Version = 0x00, Nonce = 0x01020304, - SystemFee = (long)BigInteger.Pow(10, 8), // 1 GAS + SystemFee = (long)BigInteger.Pow(10, 8), // 1 GAS NetworkFee = 0x0000000000000001, ValidUntilBlock = 0x01020304, Attributes = Array.Empty(), @@ -937,7 +954,7 @@ public void Transaction_Serialize_Deserialize_MaxSizeCosigners() { Version = 0x00, Nonce = 0x01020304, - SystemFee = (long)BigInteger.Pow(10, 8), // 1 GAS + SystemFee = (long)BigInteger.Pow(10, 8), // 1 GAS NetworkFee = 0x0000000000000001, ValidUntilBlock = 0x01020304, Attributes = Array.Empty(), @@ -1012,7 +1029,7 @@ public void FeeIsSignatureContract_TestScope_FeeOnly_Default() // Sign // ---- - var data = new ContractParametersContext(snapshot, tx, ProtocolSettings.Default.Network); + var data = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); bool signed = wallet.Sign(data); Assert.IsTrue(signed); @@ -1021,7 +1038,7 @@ public void FeeIsSignatureContract_TestScope_FeeOnly_Default() tx.Witnesses.Length.Should().Be(1); // Fast check - Assert.IsTrue(tx.VerifyWitnesses(ProtocolSettings.Default, snapshot, tx.NetworkFee)); + Assert.IsTrue(tx.VerifyWitnesses(TestProtocolSettings.Default, snapshot, tx.NetworkFee)); // Check long verificationGas = 0; @@ -1033,7 +1050,7 @@ public void FeeIsSignatureContract_TestScope_FeeOnly_Default() Assert.AreEqual(VMState.HALT, engine.Execute()); Assert.AreEqual(1, engine.ResultStack.Count); Assert.IsTrue(engine.ResultStack.Pop().GetBoolean()); - verificationGas += engine.GasConsumed; + verificationGas += engine.FeeConsumed; } // get sizeGas var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); @@ -1117,9 +1134,9 @@ public void Test_VerifyStateIndependent() } } }; - tx.VerifyStateIndependent(ProtocolSettings.Default).Should().Be(VerifyResult.OverSize); + tx.VerifyStateIndependent(TestProtocolSettings.Default).Should().Be(VerifyResult.OverSize); tx.Script = Array.Empty(); - tx.VerifyStateIndependent(ProtocolSettings.Default).Should().Be(VerifyResult.Succeed); + tx.VerifyStateIndependent(TestProtocolSettings.Default).Should().Be(VerifyResult.Succeed); var walletA = TestUtils.GenerateTestWallet("123"); var walletB = TestUtils.GenerateTestWallet("123"); @@ -1161,13 +1178,13 @@ public void Test_VerifyStateIndependent() // Sign - var data = new ContractParametersContext(snapshot, tx, ProtocolSettings.Default.Network); + var data = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); Assert.IsTrue(walletA.Sign(data)); Assert.IsTrue(walletB.Sign(data)); Assert.IsTrue(data.Completed); tx.Witnesses = data.GetWitnesses(); - tx.VerifyStateIndependent(ProtocolSettings.Default).Should().Be(VerifyResult.Succeed); + tx.VerifyStateIndependent(TestProtocolSettings.Default).Should().Be(VerifyResult.Succeed); // Different hash @@ -1176,7 +1193,7 @@ public void Test_VerifyStateIndependent() VerificationScript = walletB.GetAccounts().First().Contract.Script, InvocationScript = tx.Witnesses[0].InvocationScript.ToArray() }; - tx.VerifyStateIndependent(ProtocolSettings.Default).Should().Be(VerifyResult.Invalid); + tx.VerifyStateIndependent(TestProtocolSettings.Default).Should().Be(VerifyResult.Invalid); } [TestMethod] @@ -1250,13 +1267,13 @@ public void Test_VerifyStateDependent() // Sign - var data = new ContractParametersContext(snapshot, tx, ProtocolSettings.Default.Network); + var data = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); Assert.IsTrue(walletA.Sign(data)); Assert.IsTrue(walletB.Sign(data)); Assert.IsTrue(data.Completed); tx.Witnesses = data.GetWitnesses(); - tx.VerifyStateDependent(ProtocolSettings.Default, snapshot, new TransactionVerificationContext(), new List()).Should().Be(VerifyResult.Succeed); + tx.VerifyStateDependent(TestProtocolSettings.Default, snapshot, new TransactionVerificationContext(), new List()).Should().Be(VerifyResult.Succeed); } [TestMethod] diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_VersionPayload.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_VersionPayload.cs index 30fdb1f5a8..a77740a1e5 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_VersionPayload.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_VersionPayload.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_VersionPayload.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Witness.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Witness.cs index d142ffc927..74898eb4fb 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Witness.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Witness.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Witness.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; @@ -66,7 +77,7 @@ private static Witness PrepareDummyWitness(int pubKeys, int m) ValidUntilBlock = 0, Version = 0, Witnesses = Array.Empty() - }, ProtocolSettings.Default.Network); + }, TestProtocolSettings.Default.Network); for (int x = 0; x < m; x++) { diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_WitnessContition.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_WitnessContition.cs index 97a7ede352..113d667296 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_WitnessContition.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_WitnessContition.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_WitnessContition.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; using Neo.Json; @@ -22,7 +33,7 @@ public void TestFromJson1() } }; var json = condition.ToJson(); - var new_condi = WitnessCondition.FromJson(json); + var new_condi = WitnessCondition.FromJson(json, 2); Assert.IsTrue(new_condi is OrCondition); var or_condi = (OrCondition)new_condi; Assert.AreEqual(2, or_condi.Expressions.Length); @@ -42,7 +53,7 @@ public void TestFromJson2() var hash2 = UInt160.Parse("0xd2a4cff31913016155e38e474a2c06d08be276cf"); var jstr = "{\"type\":\"Or\",\"expressions\":[{\"type\":\"And\",\"expressions\":[{\"type\":\"CalledByContract\",\"hash\":\"0x0000000000000000000000000000000000000000\"},{\"type\":\"ScriptHash\",\"hash\":\"0xd2a4cff31913016155e38e474a2c06d08be276cf\"}]},{\"type\":\"Or\",\"expressions\":[{\"type\":\"CalledByGroup\",\"group\":\"03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c\"},{\"type\":\"Boolean\",\"expression\":true}]}]}"; var json = (JObject)JToken.Parse(jstr); - var condi = WitnessCondition.FromJson(json); + var condi = WitnessCondition.FromJson(json, 2); var or_condi = (OrCondition)condi; Assert.AreEqual(2, or_condi.Expressions.Length); var and_condi = (AndCondition)or_condi.Expressions[0]; diff --git a/tests/Neo.UnitTests/Network/P2P/UT_ChannelsConfig.cs b/tests/Neo.UnitTests/Network/P2P/UT_ChannelsConfig.cs index 5613364319..5180f33c88 100644 --- a/tests/Neo.UnitTests/Network/P2P/UT_ChannelsConfig.cs +++ b/tests/Neo.UnitTests/Network/P2P/UT_ChannelsConfig.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ChannelsConfig.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Network.P2P; @@ -14,17 +25,16 @@ public void CreateTest() var config = new ChannelsConfig(); config.Tcp.Should().BeNull(); - config.WebSocket.Should().BeNull(); config.MinDesiredConnections.Should().Be(10); config.MaxConnections.Should().Be(40); config.MaxConnectionsPerAddress.Should().Be(3); - config.Tcp = config.WebSocket = new IPEndPoint(IPAddress.Any, 21); + config.Tcp = config.Tcp = new IPEndPoint(IPAddress.Any, 21); config.MaxConnectionsPerAddress++; config.MaxConnections++; config.MinDesiredConnections++; - config.Tcp.Should().BeSameAs(config.WebSocket); + config.Tcp.Should().BeSameAs(config.Tcp); config.Tcp.Address.Should().BeEquivalentTo(IPAddress.Any); config.Tcp.Port.Should().Be(21); config.MinDesiredConnections.Should().Be(11); diff --git a/tests/Neo.UnitTests/Network/P2P/UT_LocalNode.cs b/tests/Neo.UnitTests/Network/P2P/UT_LocalNode.cs index 1e4da7590b..cd307cdc51 100644 --- a/tests/Neo.UnitTests/Network/P2P/UT_LocalNode.cs +++ b/tests/Neo.UnitTests/Network/P2P/UT_LocalNode.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_LocalNode.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Akka.TestKit.Xunit2; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Network.P2P; @@ -26,7 +37,6 @@ public void TestDefaults() var localnode = senderProbe.ExpectMsg(); Assert.AreEqual(0, localnode.ListenerTcpPort); - Assert.AreEqual(0, localnode.ListenerWsPort); Assert.AreEqual(3, localnode.MaxConnectionsPerAddress); Assert.AreEqual(10, localnode.MinDesiredConnections); Assert.AreEqual(40, localnode.MaxConnections); diff --git a/tests/Neo.UnitTests/Network/P2P/UT_Message.cs b/tests/Neo.UnitTests/Network/P2P/UT_Message.cs index b1afb98533..35f99fc44f 100644 --- a/tests/Neo.UnitTests/Network/P2P/UT_Message.cs +++ b/tests/Neo.UnitTests/Network/P2P/UT_Message.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Message.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Akka.IO; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/tests/Neo.UnitTests/Network/P2P/UT_RemoteNode.cs b/tests/Neo.UnitTests/Network/P2P/UT_RemoteNode.cs index 4e514e99a3..9f114e2708 100644 --- a/tests/Neo.UnitTests/Network/P2P/UT_RemoteNode.cs +++ b/tests/Neo.UnitTests/Network/P2P/UT_RemoteNode.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_RemoteNode.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Akka.IO; using Akka.TestKit.Xunit2; using FluentAssertions; @@ -61,7 +72,7 @@ public void RemoteNode_Test_Accept_IfSameNetwork() { UserAgent = "Unit Test".PadLeft(1024, '0'), Nonce = 1, - Network = ProtocolSettings.Default.Network, + Network = TestProtocolSettings.Default.Network, Timestamp = 5, Version = 6, Capabilities = new NodeCapability[] diff --git a/tests/Neo.UnitTests/Network/P2P/UT_RemoteNodeMailbox.cs b/tests/Neo.UnitTests/Network/P2P/UT_RemoteNodeMailbox.cs index a5174d6cfe..23e0dad367 100644 --- a/tests/Neo.UnitTests/Network/P2P/UT_RemoteNodeMailbox.cs +++ b/tests/Neo.UnitTests/Network/P2P/UT_RemoteNodeMailbox.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_RemoteNodeMailbox.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Akka.IO; using Akka.TestKit.Xunit2; using FluentAssertions; diff --git a/tests/Neo.UnitTests/Network/P2P/UT_TaskManagerMailbox.cs b/tests/Neo.UnitTests/Network/P2P/UT_TaskManagerMailbox.cs index 755bf979c1..41b4635d8c 100644 --- a/tests/Neo.UnitTests/Network/P2P/UT_TaskManagerMailbox.cs +++ b/tests/Neo.UnitTests/Network/P2P/UT_TaskManagerMailbox.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_TaskManagerMailbox.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Akka.TestKit.Xunit2; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/tests/Neo.UnitTests/Network/P2P/UT_TaskSession.cs b/tests/Neo.UnitTests/Network/P2P/UT_TaskSession.cs index bee2bc8efe..4614ba47aa 100644 --- a/tests/Neo.UnitTests/Network/P2P/UT_TaskSession.cs +++ b/tests/Neo.UnitTests/Network/P2P/UT_TaskSession.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_TaskSession.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Network.P2P; using Neo.Network.P2P.Capabilities; diff --git a/tests/Neo.UnitTests/Network/UT_UPnP.cs b/tests/Neo.UnitTests/Network/UT_UPnP.cs index 897d48ffa2..f63fb43d93 100644 --- a/tests/Neo.UnitTests/Network/UT_UPnP.cs +++ b/tests/Neo.UnitTests/Network/UT_UPnP.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_UPnP.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Network; using System; diff --git a/tests/Neo.UnitTests/Persistence/UT_MemoryStore.cs b/tests/Neo.UnitTests/Persistence/UT_MemoryStore.cs index 7dd698e1c2..b0f61791f6 100644 --- a/tests/Neo.UnitTests/Persistence/UT_MemoryStore.cs +++ b/tests/Neo.UnitTests/Persistence/UT_MemoryStore.cs @@ -1,39 +1,73 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_MemoryStore.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Persistence; +using Neo.SmartContract; using System; using System.Linq; +using System.Text; namespace Neo.UnitTests.Persistence { [TestClass] public class UT_MemoryStore { + [TestMethod] + public void LoadStoreTest() + { + Assert.IsInstanceOfType(TestBlockchain.TheNeoSystem.LoadStore("abc")); + } + [TestMethod] public void StoreTest() { using var store = new MemoryStore(); - store.Delete(new byte[] { 1 }); - Assert.AreEqual(null, store.TryGet(new byte[] { 1 })); - store.Put(new byte[] { 1 }, new byte[] { 1, 2, 3 }); - CollectionAssert.AreEqual(new byte[] { 1, 2, 3 }, store.TryGet(new byte[] { 1 })); + store.Delete([1]); + Assert.AreEqual(null, store.TryGet([1])); + store.Put([1], [1, 2, 3]); + CollectionAssert.AreEqual(new byte[] { 1, 2, 3 }, store.TryGet([1])); - store.Put(new byte[] { 2 }, new byte[] { 4, 5, 6 }); - CollectionAssert.AreEqual(new byte[] { 1 }, store.Seek(Array.Empty(), SeekDirection.Forward).Select(u => u.Key).First()); - CollectionAssert.AreEqual(new byte[] { 2 }, store.Seek(new byte[] { 2 }, SeekDirection.Backward).Select(u => u.Key).First()); - CollectionAssert.AreEqual(new byte[] { 1 }, store.Seek(new byte[] { 1 }, SeekDirection.Backward).Select(u => u.Key).First()); + store.Put([2], [4, 5, 6]); + CollectionAssert.AreEqual(new byte[] { 1 }, store.Seek(Array.Empty()).Select(u => u.Key).First()); + CollectionAssert.AreEqual(new byte[] { 2 }, store.Seek([2], SeekDirection.Backward).Select(u => u.Key).First()); + CollectionAssert.AreEqual(new byte[] { 1 }, store.Seek([1], SeekDirection.Backward).Select(u => u.Key).First()); - store.Delete(new byte[] { 1 }); - store.Delete(new byte[] { 2 }); + store.Delete([1]); + store.Delete([2]); - store.Put(new byte[] { 0x00, 0x00, 0x00 }, new byte[] { 0x00 }); - store.Put(new byte[] { 0x00, 0x00, 0x01 }, new byte[] { 0x01 }); - store.Put(new byte[] { 0x00, 0x00, 0x02 }, new byte[] { 0x02 }); - store.Put(new byte[] { 0x00, 0x00, 0x03 }, new byte[] { 0x03 }); - store.Put(new byte[] { 0x00, 0x00, 0x04 }, new byte[] { 0x04 }); + store.Put([0x00, 0x00, 0x00], [0x00]); + store.Put([0x00, 0x00, 0x01], [0x01]); + store.Put([0x00, 0x00, 0x02], [0x02]); + store.Put([0x00, 0x00, 0x03], [0x03]); + store.Put([0x00, 0x00, 0x04], [0x04]); var entries = store.Seek(Array.Empty(), SeekDirection.Backward).ToArray(); - Assert.AreEqual(entries.Count(), 0); + Assert.AreEqual(entries.Length, 0); } + + [TestMethod] + public void NeoSystemStoreViewTest() + { + var neoSystem = new NeoSystem(TestProtocolSettings.Default, new MemoryStoreProvider()); + Assert.IsNotNull(neoSystem.StoreView); + var store = neoSystem.StoreView; + var key = new StorageKey(Encoding.UTF8.GetBytes("testKey")); + var value = new StorageItem(Encoding.UTF8.GetBytes("testValue")); + store.Add(key, value); + store.Commit(); + var result = store.TryGet(key); + Assert.AreEqual("testValue", Encoding.UTF8.GetString(result.Value.ToArray())); + } + } } diff --git a/tests/Neo.UnitTests/Plugins/TestPlugin.cs b/tests/Neo.UnitTests/Plugins/TestPlugin.cs index f2d0606923..dde500b927 100644 --- a/tests/Neo.UnitTests/Plugins/TestPlugin.cs +++ b/tests/Neo.UnitTests/Plugins/TestPlugin.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TestPlugin.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Microsoft.Extensions.Configuration; using Neo.Plugins; diff --git a/tests/Neo.UnitTests/Plugins/UT_Plugin.cs b/tests/Neo.UnitTests/Plugins/UT_Plugin.cs index 0ed00ac658..e5e8cb6e49 100644 --- a/tests/Neo.UnitTests/Plugins/UT_Plugin.cs +++ b/tests/Neo.UnitTests/Plugins/UT_Plugin.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Plugin.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Plugins; diff --git a/tests/Neo.UnitTests/SmartContract/Iterators/UT_StorageIterator.cs b/tests/Neo.UnitTests/SmartContract/Iterators/UT_StorageIterator.cs index 9abef9fb52..16f6b11973 100644 --- a/tests/Neo.UnitTests/SmartContract/Iterators/UT_StorageIterator.cs +++ b/tests/Neo.UnitTests/SmartContract/Iterators/UT_StorageIterator.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_StorageIterator.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.SmartContract; diff --git a/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractEventDescriptor.cs b/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractEventDescriptor.cs index a829354e34..c04f4a009e 100644 --- a/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractEventDescriptor.cs +++ b/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractEventDescriptor.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ContractEventDescriptor.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.SmartContract.Manifest; diff --git a/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractGroup.cs b/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractGroup.cs index 27094d8cef..b612714e7c 100644 --- a/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractGroup.cs +++ b/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractGroup.cs @@ -1,10 +1,20 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ContractGroup.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; using Neo.SmartContract; using Neo.SmartContract.Manifest; using Neo.Wallets; using System; -using System.Linq; namespace Neo.UnitTests.SmartContract.Manifest { @@ -33,7 +43,7 @@ public void TestClone() public void TestIsValid() { Random random = new(); - byte[] privateKey = new byte[32]; + var privateKey = new byte[32]; random.NextBytes(privateKey); KeyPair keyPair = new(privateKey); ContractGroup contractGroup = new() @@ -44,11 +54,11 @@ public void TestIsValid() Assert.AreEqual(false, contractGroup.IsValid(UInt160.Zero)); - byte[] message = new byte[] { 0x01,0x01,0x01,0x01,0x01, + var message = new byte[] { 0x01,0x01,0x01,0x01,0x01, 0x01,0x01,0x01,0x01,0x01, 0x01,0x01,0x01,0x01,0x01, 0x01,0x01,0x01,0x01,0x01 }; - byte[] signature = Crypto.Sign(message, keyPair.PrivateKey, keyPair.PublicKey.EncodePoint(false).Skip(1).ToArray()); + var signature = Crypto.Sign(message, keyPair.PrivateKey); contractGroup = new ContractGroup { PubKey = keyPair.PublicKey, diff --git a/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs b/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs index 986716e694..4e2e866281 100644 --- a/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs +++ b/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs @@ -1,4 +1,14 @@ -using System; +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ContractManifest.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; using Neo.IO; @@ -6,12 +16,33 @@ using Neo.SmartContract; using Neo.SmartContract.Manifest; using Neo.VM; +using System; namespace Neo.UnitTests.SmartContract.Manifest { [TestClass] public class UT_ContractManifest { + [TestMethod] + public void TestMainnetContract() + { + // 0x6f1837723768f27a6f6a14452977e3e0e264f2cc in mainnet + var json = @"{""name"":""Test"",""groups"":[],""features"":{},""supportedstandards"":[],""abi"":{""methods"":[{""name"":""a"",""parameters"":[{""name"":""amountIn"",""type"":""Integer""}],""returntype"":""Void"",""offset"":0,""safe"":false},{""name"":""a0"",""parameters"":[],""returntype"":""Integer"",""offset"":77,""safe"":false},{""name"":""a10"",""parameters"":[],""returntype"":""Void"",""offset"":378,""safe"":false},{""name"":""a11"",""parameters"":[],""returntype"":""Void"",""offset"":398,""safe"":false},{""name"":""a12"",""parameters"":[],""returntype"":""Void"",""offset"":418,""safe"":false},{""name"":""a13"",""parameters"":[],""returntype"":""Void"",""offset"":438,""safe"":false},{""name"":""a14"",""parameters"":[],""returntype"":""Void"",""offset"":458,""safe"":false},{""name"":""a15"",""parameters"":[],""returntype"":""Void"",""offset"":478,""safe"":false},{""name"":""a16"",""parameters"":[],""returntype"":""Void"",""offset"":498,""safe"":false},{""name"":""a17"",""parameters"":[],""returntype"":""Void"",""offset"":518,""safe"":false},{""name"":""a18"",""parameters"":[],""returntype"":""Void"",""offset"":539,""safe"":false},{""name"":""a19"",""parameters"":[],""returntype"":""Void"",""offset"":560,""safe"":false},{""name"":""a20"",""parameters"":[],""returntype"":""Void"",""offset"":581,""safe"":false},{""name"":""a21"",""parameters"":[],""returntype"":""Void"",""offset"":602,""safe"":false},{""name"":""a22"",""parameters"":[],""returntype"":""Void"",""offset"":623,""safe"":false},{""name"":""a23"",""parameters"":[],""returntype"":""Void"",""offset"":644,""safe"":false},{""name"":""a24"",""parameters"":[],""returntype"":""Void"",""offset"":665,""safe"":false},{""name"":""a25"",""parameters"":[],""returntype"":""Void"",""offset"":686,""safe"":false},{""name"":""a26"",""parameters"":[],""returntype"":""Void"",""offset"":707,""safe"":false},{""name"":""a27"",""parameters"":[],""returntype"":""Void"",""offset"":728,""safe"":false},{""name"":""a28"",""parameters"":[],""returntype"":""Void"",""offset"":749,""safe"":false},{""name"":""a29"",""parameters"":[],""returntype"":""Void"",""offset"":770,""safe"":false},{""name"":""a30"",""parameters"":[],""returntype"":""Void"",""offset"":791,""safe"":false},{""name"":""a31"",""parameters"":[],""returntype"":""Void"",""offset"":812,""safe"":false},{""name"":""a32"",""parameters"":[],""returntype"":""Void"",""offset"":833,""safe"":false},{""name"":""a33"",""parameters"":[],""returntype"":""Void"",""offset"":854,""safe"":false},{""name"":""a34"",""parameters"":[],""returntype"":""Void"",""offset"":875,""safe"":false},{""name"":""a35"",""parameters"":[],""returntype"":""Void"",""offset"":896,""safe"":false},{""name"":""a36"",""parameters"":[],""returntype"":""Void"",""offset"":917,""safe"":false},{""name"":""a37"",""parameters"":[],""returntype"":""Void"",""offset"":938,""safe"":false},{""name"":""a38"",""parameters"":[],""returntype"":""Void"",""offset"":959,""safe"":false},{""name"":""a39"",""parameters"":[],""returntype"":""Void"",""offset"":980,""safe"":false},{""name"":""a40"",""parameters"":[],""returntype"":""Void"",""offset"":1001,""safe"":false},{""name"":""a41"",""parameters"":[],""returntype"":""Void"",""offset"":1022,""safe"":false},{""name"":""a42"",""parameters"":[],""returntype"":""Void"",""offset"":1043,""safe"":false},{""name"":""a43"",""parameters"":[],""returntype"":""Void"",""offset"":1064,""safe"":false},{""name"":""a44"",""parameters"":[],""returntype"":""Void"",""offset"":1085,""safe"":false},{""name"":""a45"",""parameters"":[],""returntype"":""Void"",""offset"":1106,""safe"":false},{""name"":""a46"",""parameters"":[],""returntype"":""Void"",""offset"":1127,""safe"":false},{""name"":""a47"",""parameters"":[],""returntype"":""Void"",""offset"":1148,""safe"":false},{""name"":""a48"",""parameters"":[],""returntype"":""Void"",""offset"":1169,""safe"":false},{""name"":""a49"",""parameters"":[],""returntype"":""Void"",""offset"":1190,""safe"":false},{""name"":""a50"",""parameters"":[],""returntype"":""Void"",""offset"":1211,""safe"":false},{""name"":""b"",""parameters"":[{""name"":""amountIn"",""type"":""Integer""}],""returntype"":""Void"",""offset"":1232,""safe"":false},{""name"":""b0"",""parameters"":[],""returntype"":""Integer"",""offset"":1251,""safe"":false},{""name"":""b10"",""parameters"":[],""returntype"":""Void"",""offset"":1275,""safe"":false},{""name"":""b11"",""parameters"":[],""returntype"":""Void"",""offset"":1295,""safe"":false},{""name"":""b12"",""parameters"":[],""returntype"":""Void"",""offset"":1315,""safe"":false},{""name"":""b13"",""parameters"":[],""returntype"":""Void"",""offset"":1335,""safe"":false},{""name"":""b14"",""parameters"":[],""returntype"":""Void"",""offset"":1355,""safe"":false},{""name"":""b15"",""parameters"":[],""returntype"":""Void"",""offset"":1375,""safe"":false},{""name"":""b16"",""parameters"":[],""returntype"":""Void"",""offset"":1395,""safe"":false},{""name"":""b17"",""parameters"":[],""returntype"":""Void"",""offset"":1415,""safe"":false},{""name"":""b18"",""parameters"":[],""returntype"":""Void"",""offset"":1436,""safe"":false},{""name"":""b19"",""parameters"":[],""returntype"":""Void"",""offset"":1457,""safe"":false},{""name"":""b20"",""parameters"":[],""returntype"":""Void"",""offset"":1478,""safe"":false},{""name"":""b21"",""parameters"":[],""returntype"":""Void"",""offset"":1499,""safe"":false},{""name"":""b22"",""parameters"":[],""returntype"":""Void"",""offset"":1520,""safe"":false},{""name"":""b23"",""parameters"":[],""returntype"":""Void"",""offset"":1541,""safe"":false},{""name"":""b24"",""parameters"":[],""returntype"":""Void"",""offset"":1562,""safe"":false},{""name"":""b25"",""parameters"":[],""returntype"":""Void"",""offset"":1583,""safe"":false},{""name"":""b26"",""parameters"":[],""returntype"":""Void"",""offset"":1604,""safe"":false},{""name"":""b27"",""parameters"":[],""returntype"":""Void"",""offset"":1625,""safe"":false},{""name"":""b28"",""parameters"":[],""returntype"":""Void"",""offset"":1646,""safe"":false},{""name"":""b29"",""parameters"":[],""returntype"":""Void"",""offset"":1667,""safe"":false},{""name"":""b30"",""parameters"":[],""returntype"":""Void"",""offset"":1688,""safe"":false},{""name"":""b31"",""parameters"":[],""returntype"":""Void"",""offset"":1709,""safe"":false},{""name"":""b32"",""parameters"":[],""returntype"":""Void"",""offset"":1730,""safe"":false},{""name"":""b33"",""parameters"":[],""returntype"":""Void"",""offset"":1751,""safe"":false},{""name"":""b34"",""parameters"":[],""returntype"":""Void"",""offset"":1772,""safe"":false},{""name"":""b35"",""parameters"":[],""returntype"":""Void"",""offset"":1793,""safe"":false},{""name"":""b36"",""parameters"":[],""returntype"":""Void"",""offset"":1814,""safe"":false},{""name"":""b37"",""parameters"":[],""returntype"":""Void"",""offset"":1835,""safe"":false},{""name"":""b38"",""parameters"":[],""returntype"":""Void"",""offset"":1856,""safe"":false},{""name"":""b39"",""parameters"":[],""returntype"":""Void"",""offset"":1877,""safe"":false},{""name"":""b40"",""parameters"":[],""returntype"":""Void"",""offset"":1898,""safe"":false},{""name"":""b41"",""parameters"":[],""returntype"":""Void"",""offset"":1919,""safe"":false},{""name"":""b42"",""parameters"":[],""returntype"":""Void"",""offset"":1940,""safe"":false},{""name"":""b43"",""parameters"":[],""returntype"":""Void"",""offset"":1961,""safe"":false},{""name"":""b44"",""parameters"":[],""returntype"":""Void"",""offset"":1982,""safe"":false},{""name"":""b45"",""parameters"":[],""returntype"":""Void"",""offset"":2003,""safe"":false},{""name"":""b46"",""parameters"":[],""returntype"":""Void"",""offset"":2024,""safe"":false},{""name"":""b47"",""parameters"":[],""returntype"":""Void"",""offset"":2045,""safe"":false},{""name"":""b48"",""parameters"":[],""returntype"":""Void"",""offset"":2066,""safe"":false},{""name"":""b49"",""parameters"":[],""returntype"":""Void"",""offset"":2087,""safe"":false},{""name"":""b50"",""parameters"":[],""returntype"":""Void"",""offset"":2108,""safe"":false},{""name"":""c"",""parameters"":[{""name"":""amountIn"",""type"":""Integer""}],""returntype"":""Void"",""offset"":2129,""safe"":false},{""name"":""c0"",""parameters"":[],""returntype"":""Integer"",""offset"":2148,""safe"":false},{""name"":""c10"",""parameters"":[],""returntype"":""Void"",""offset"":2172,""safe"":false},{""name"":""c11"",""parameters"":[],""returntype"":""Void"",""offset"":2192,""safe"":false},{""name"":""c12"",""parameters"":[],""returntype"":""Void"",""offset"":2212,""safe"":false},{""name"":""c13"",""parameters"":[],""returntype"":""Void"",""offset"":2232,""safe"":false},{""name"":""c14"",""parameters"":[],""returntype"":""Void"",""offset"":2252,""safe"":false},{""name"":""c15"",""parameters"":[],""returntype"":""Void"",""offset"":2272,""safe"":false},{""name"":""c16"",""parameters"":[],""returntype"":""Void"",""offset"":2292,""safe"":false},{""name"":""c17"",""parameters"":[],""returntype"":""Void"",""offset"":2312,""safe"":false},{""name"":""c18"",""parameters"":[],""returntype"":""Void"",""offset"":2333,""safe"":false},{""name"":""c19"",""parameters"":[],""returntype"":""Void"",""offset"":2354,""safe"":false},{""name"":""c20"",""parameters"":[],""returntype"":""Void"",""offset"":2375,""safe"":false},{""name"":""c21"",""parameters"":[],""returntype"":""Void"",""offset"":2396,""safe"":false},{""name"":""c22"",""parameters"":[],""returntype"":""Void"",""offset"":2417,""safe"":false},{""name"":""c23"",""parameters"":[],""returntype"":""Void"",""offset"":2438,""safe"":false},{""name"":""c24"",""parameters"":[],""returntype"":""Void"",""offset"":2459,""safe"":false},{""name"":""c25"",""parameters"":[],""returntype"":""Void"",""offset"":2480,""safe"":false},{""name"":""c26"",""parameters"":[],""returntype"":""Void"",""offset"":2501,""safe"":false},{""name"":""c27"",""parameters"":[],""returntype"":""Void"",""offset"":2522,""safe"":false},{""name"":""c28"",""parameters"":[],""returntype"":""Void"",""offset"":2543,""safe"":false},{""name"":""c29"",""parameters"":[],""returntype"":""Void"",""offset"":2564,""safe"":false},{""name"":""c30"",""parameters"":[],""returntype"":""Void"",""offset"":2585,""safe"":false},{""name"":""c31"",""parameters"":[],""returntype"":""Void"",""offset"":2606,""safe"":false},{""name"":""c32"",""parameters"":[],""returntype"":""Void"",""offset"":2627,""safe"":false},{""name"":""c33"",""parameters"":[],""returntype"":""Void"",""offset"":2648,""safe"":false},{""name"":""c34"",""parameters"":[],""returntype"":""Void"",""offset"":2669,""safe"":false},{""name"":""c35"",""parameters"":[],""returntype"":""Void"",""offset"":2690,""safe"":false},{""name"":""c36"",""parameters"":[],""returntype"":""Void"",""offset"":2711,""safe"":false},{""name"":""c37"",""parameters"":[],""returntype"":""Void"",""offset"":2732,""safe"":false},{""name"":""c38"",""parameters"":[],""returntype"":""Void"",""offset"":2753,""safe"":false},{""name"":""c39"",""parameters"":[],""returntype"":""Void"",""offset"":2774,""safe"":false},{""name"":""c40"",""parameters"":[],""returntype"":""Void"",""offset"":2795,""safe"":false},{""name"":""c41"",""parameters"":[],""returntype"":""Void"",""offset"":2816,""safe"":false},{""name"":""c42"",""parameters"":[],""returntype"":""Void"",""offset"":2837,""safe"":false},{""name"":""c43"",""parameters"":[],""returntype"":""Void"",""offset"":2858,""safe"":false},{""name"":""c44"",""parameters"":[],""returntype"":""Void"",""offset"":2879,""safe"":false},{""name"":""c45"",""parameters"":[],""returntype"":""Void"",""offset"":2900,""safe"":false},{""name"":""c46"",""parameters"":[],""returntype"":""Void"",""offset"":2921,""safe"":false},{""name"":""c47"",""parameters"":[],""returntype"":""Void"",""offset"":2942,""safe"":false},{""name"":""c48"",""parameters"":[],""returntype"":""Void"",""offset"":2963,""safe"":false},{""name"":""c49"",""parameters"":[],""returntype"":""Void"",""offset"":2984,""safe"":false},{""name"":""c50"",""parameters"":[],""returntype"":""Void"",""offset"":3005,""safe"":false},{""name"":""verify"",""parameters"":[],""returntype"":""Boolean"",""offset"":3026,""safe"":false},{""name"":""d"",""parameters"":[{""name"":""amountIn"",""type"":""Integer""}],""returntype"":""Void"",""offset"":3039,""safe"":false},{""name"":""d0"",""parameters"":[],""returntype"":""Integer"",""offset"":3058,""safe"":false},{""name"":""d10"",""parameters"":[],""returntype"":""Void"",""offset"":3082,""safe"":false},{""name"":""d11"",""parameters"":[],""returntype"":""Void"",""offset"":3102,""safe"":false},{""name"":""d12"",""parameters"":[],""returntype"":""Void"",""offset"":3122,""safe"":false},{""name"":""d13"",""parameters"":[],""returntype"":""Void"",""offset"":3142,""safe"":false},{""name"":""d14"",""parameters"":[],""returntype"":""Void"",""offset"":3162,""safe"":false},{""name"":""d15"",""parameters"":[],""returntype"":""Void"",""offset"":3182,""safe"":false},{""name"":""d16"",""parameters"":[],""returntype"":""Void"",""offset"":3202,""safe"":false},{""name"":""d17"",""parameters"":[],""returntype"":""Void"",""offset"":3222,""safe"":false},{""name"":""d18"",""parameters"":[],""returntype"":""Void"",""offset"":3243,""safe"":false},{""name"":""d19"",""parameters"":[],""returntype"":""Void"",""offset"":3264,""safe"":false},{""name"":""d20"",""parameters"":[],""returntype"":""Void"",""offset"":3285,""safe"":false},{""name"":""d21"",""parameters"":[],""returntype"":""Void"",""offset"":3306,""safe"":false},{""name"":""d22"",""parameters"":[],""returntype"":""Void"",""offset"":3327,""safe"":false},{""name"":""d23"",""parameters"":[],""returntype"":""Void"",""offset"":3348,""safe"":false},{""name"":""d24"",""parameters"":[],""returntype"":""Void"",""offset"":3369,""safe"":false},{""name"":""d25"",""parameters"":[],""returntype"":""Void"",""offset"":3390,""safe"":false},{""name"":""d26"",""parameters"":[],""returntype"":""Void"",""offset"":3411,""safe"":false},{""name"":""d27"",""parameters"":[],""returntype"":""Void"",""offset"":3432,""safe"":false},{""name"":""d28"",""parameters"":[],""returntype"":""Void"",""offset"":3453,""safe"":false},{""name"":""d29"",""parameters"":[],""returntype"":""Void"",""offset"":3474,""safe"":false},{""name"":""d30"",""parameters"":[],""returntype"":""Void"",""offset"":3495,""safe"":false},{""name"":""d31"",""parameters"":[],""returntype"":""Void"",""offset"":3516,""safe"":false},{""name"":""d32"",""parameters"":[],""returntype"":""Void"",""offset"":3537,""safe"":false},{""name"":""d33"",""parameters"":[],""returntype"":""Void"",""offset"":3558,""safe"":false},{""name"":""d34"",""parameters"":[],""returntype"":""Void"",""offset"":3579,""safe"":false},{""name"":""d35"",""parameters"":[],""returntype"":""Void"",""offset"":3600,""safe"":false},{""name"":""d36"",""parameters"":[],""returntype"":""Void"",""offset"":3621,""safe"":false},{""name"":""d37"",""parameters"":[],""returntype"":""Void"",""offset"":3642,""safe"":false},{""name"":""d38"",""parameters"":[],""returntype"":""Void"",""offset"":3663,""safe"":false},{""name"":""d39"",""parameters"":[],""returntype"":""Void"",""offset"":3684,""safe"":false},{""name"":""d40"",""parameters"":[],""returntype"":""Void"",""offset"":3705,""safe"":false},{""name"":""d41"",""parameters"":[],""returntype"":""Void"",""offset"":3726,""safe"":false},{""name"":""d42"",""parameters"":[],""returntype"":""Void"",""offset"":3747,""safe"":false},{""name"":""d43"",""parameters"":[],""returntype"":""Void"",""offset"":3768,""safe"":false},{""name"":""d44"",""parameters"":[],""returntype"":""Void"",""offset"":3789,""safe"":false},{""name"":""d45"",""parameters"":[],""returntype"":""Void"",""offset"":3810,""safe"":false},{""name"":""d46"",""parameters"":[],""returntype"":""Void"",""offset"":3831,""safe"":false},{""name"":""d47"",""parameters"":[],""returntype"":""Void"",""offset"":3852,""safe"":false},{""name"":""d48"",""parameters"":[],""returntype"":""Void"",""offset"":3873,""safe"":false},{""name"":""d49"",""parameters"":[],""returntype"":""Void"",""offset"":3894,""safe"":false},{""name"":""d50"",""parameters"":[],""returntype"":""Void"",""offset"":3915,""safe"":false},{""name"":""e"",""parameters"":[{""name"":""amountIn"",""type"":""Integer""}],""returntype"":""Void"",""offset"":3936,""safe"":false},{""name"":""e0"",""parameters"":[],""returntype"":""Integer"",""offset"":3955,""safe"":false},{""name"":""e10"",""parameters"":[],""returntype"":""Void"",""offset"":3979,""safe"":false},{""name"":""e11"",""parameters"":[],""returntype"":""Void"",""offset"":3999,""safe"":false},{""name"":""e12"",""parameters"":[],""returntype"":""Void"",""offset"":4019,""safe"":false},{""name"":""e13"",""parameters"":[],""returntype"":""Void"",""offset"":4039,""safe"":false},{""name"":""e14"",""parameters"":[],""returntype"":""Void"",""offset"":4059,""safe"":false},{""name"":""e15"",""parameters"":[],""returntype"":""Void"",""offset"":4079,""safe"":false},{""name"":""e16"",""parameters"":[],""returntype"":""Void"",""offset"":4099,""safe"":false},{""name"":""e17"",""parameters"":[],""returntype"":""Void"",""offset"":4119,""safe"":false},{""name"":""e18"",""parameters"":[],""returntype"":""Void"",""offset"":4140,""safe"":false},{""name"":""e19"",""parameters"":[],""returntype"":""Void"",""offset"":4161,""safe"":false},{""name"":""e20"",""parameters"":[],""returntype"":""Void"",""offset"":4182,""safe"":false},{""name"":""e21"",""parameters"":[],""returntype"":""Void"",""offset"":4203,""safe"":false},{""name"":""e22"",""parameters"":[],""returntype"":""Void"",""offset"":4224,""safe"":false},{""name"":""e23"",""parameters"":[],""returntype"":""Void"",""offset"":4245,""safe"":false},{""name"":""e24"",""parameters"":[],""returntype"":""Void"",""offset"":4266,""safe"":false},{""name"":""e25"",""parameters"":[],""returntype"":""Void"",""offset"":4287,""safe"":false},{""name"":""e26"",""parameters"":[],""returntype"":""Void"",""offset"":4308,""safe"":false},{""name"":""e27"",""parameters"":[],""returntype"":""Void"",""offset"":4329,""safe"":false},{""name"":""e28"",""parameters"":[],""returntype"":""Void"",""offset"":4350,""safe"":false},{""name"":""e29"",""parameters"":[],""returntype"":""Void"",""offset"":4371,""safe"":false},{""name"":""e30"",""parameters"":[],""returntype"":""Void"",""offset"":4392,""safe"":false},{""name"":""e31"",""parameters"":[],""returntype"":""Void"",""offset"":4413,""safe"":false},{""name"":""e32"",""parameters"":[],""returntype"":""Void"",""offset"":4434,""safe"":false},{""name"":""e33"",""parameters"":[],""returntype"":""Void"",""offset"":4455,""safe"":false},{""name"":""e34"",""parameters"":[],""returntype"":""Void"",""offset"":4476,""safe"":false},{""name"":""e35"",""parameters"":[],""returntype"":""Void"",""offset"":4497,""safe"":false},{""name"":""e36"",""parameters"":[],""returntype"":""Void"",""offset"":4518,""safe"":false},{""name"":""e37"",""parameters"":[],""returntype"":""Void"",""offset"":4539,""safe"":false},{""name"":""e38"",""parameters"":[],""returntype"":""Void"",""offset"":4560,""safe"":false},{""name"":""e39"",""parameters"":[],""returntype"":""Void"",""offset"":4581,""safe"":false},{""name"":""e40"",""parameters"":[],""returntype"":""Void"",""offset"":4602,""safe"":false},{""name"":""e41"",""parameters"":[],""returntype"":""Void"",""offset"":4623,""safe"":false},{""name"":""e42"",""parameters"":[],""returntype"":""Void"",""offset"":4644,""safe"":false},{""name"":""e43"",""parameters"":[],""returntype"":""Void"",""offset"":4665,""safe"":false},{""name"":""e44"",""parameters"":[],""returntype"":""Void"",""offset"":4686,""safe"":false},{""name"":""e45"",""parameters"":[],""returntype"":""Void"",""offset"":4707,""safe"":false},{""name"":""e46"",""parameters"":[],""returntype"":""Void"",""offset"":4728,""safe"":false},{""name"":""e47"",""parameters"":[],""returntype"":""Void"",""offset"":4749,""safe"":false},{""name"":""e48"",""parameters"":[],""returntype"":""Void"",""offset"":4770,""safe"":false},{""name"":""e49"",""parameters"":[],""returntype"":""Void"",""offset"":4791,""safe"":false},{""name"":""e50"",""parameters"":[],""returntype"":""Void"",""offset"":4812,""safe"":false},{""name"":""f"",""parameters"":[{""name"":""amountIn"",""type"":""Integer""}],""returntype"":""Void"",""offset"":4833,""safe"":false},{""name"":""f0"",""parameters"":[],""returntype"":""Integer"",""offset"":4852,""safe"":false},{""name"":""f10"",""parameters"":[],""returntype"":""Void"",""offset"":4876,""safe"":false},{""name"":""f11"",""parameters"":[],""returntype"":""Void"",""offset"":4896,""safe"":false},{""name"":""f12"",""parameters"":[],""returntype"":""Void"",""offset"":4916,""safe"":false},{""name"":""f13"",""parameters"":[],""returntype"":""Void"",""offset"":4936,""safe"":false},{""name"":""f14"",""parameters"":[],""returntype"":""Void"",""offset"":4956,""safe"":false},{""name"":""f15"",""parameters"":[],""returntype"":""Void"",""offset"":4976,""safe"":false},{""name"":""f16"",""parameters"":[],""returntype"":""Void"",""offset"":4996,""safe"":false},{""name"":""f17"",""parameters"":[],""returntype"":""Void"",""offset"":5016,""safe"":false},{""name"":""f18"",""parameters"":[],""returntype"":""Void"",""offset"":5037,""safe"":false},{""name"":""f19"",""parameters"":[],""returntype"":""Void"",""offset"":5058,""safe"":false},{""name"":""f20"",""parameters"":[],""returntype"":""Void"",""offset"":5079,""safe"":false},{""name"":""f21"",""parameters"":[],""returntype"":""Void"",""offset"":5100,""safe"":false},{""name"":""f22"",""parameters"":[],""returntype"":""Void"",""offset"":5121,""safe"":false},{""name"":""f23"",""parameters"":[],""returntype"":""Void"",""offset"":5142,""safe"":false},{""name"":""f24"",""parameters"":[],""returntype"":""Void"",""offset"":5163,""safe"":false},{""name"":""f25"",""parameters"":[],""returntype"":""Void"",""offset"":5184,""safe"":false},{""name"":""f26"",""parameters"":[],""returntype"":""Void"",""offset"":5205,""safe"":false},{""name"":""f27"",""parameters"":[],""returntype"":""Void"",""offset"":5226,""safe"":false},{""name"":""f28"",""parameters"":[],""returntype"":""Void"",""offset"":5247,""safe"":false},{""name"":""f29"",""parameters"":[],""returntype"":""Void"",""offset"":5268,""safe"":false},{""name"":""f30"",""parameters"":[],""returntype"":""Void"",""offset"":5289,""safe"":false},{""name"":""f31"",""parameters"":[],""returntype"":""Void"",""offset"":5310,""safe"":false},{""name"":""f32"",""parameters"":[],""returntype"":""Void"",""offset"":5331,""safe"":false},{""name"":""f33"",""parameters"":[],""returntype"":""Void"",""offset"":5352,""safe"":false},{""name"":""f34"",""parameters"":[],""returntype"":""Void"",""offset"":5373,""safe"":false},{""name"":""f35"",""parameters"":[],""returntype"":""Void"",""offset"":5394,""safe"":false},{""name"":""f36"",""parameters"":[],""returntype"":""Void"",""offset"":5415,""safe"":false},{""name"":""f37"",""parameters"":[],""returntype"":""Void"",""offset"":5436,""safe"":false},{""name"":""f38"",""parameters"":[],""returntype"":""Void"",""offset"":5457,""safe"":false},{""name"":""f39"",""parameters"":[],""returntype"":""Void"",""offset"":5478,""safe"":false},{""name"":""f40"",""parameters"":[],""returntype"":""Void"",""offset"":5499,""safe"":false},{""name"":""f41"",""parameters"":[],""returntype"":""Void"",""offset"":5520,""safe"":false},{""name"":""f42"",""parameters"":[],""returntype"":""Void"",""offset"":5541,""safe"":false},{""name"":""f43"",""parameters"":[],""returntype"":""Void"",""offset"":5562,""safe"":false},{""name"":""f44"",""parameters"":[],""returntype"":""Void"",""offset"":5583,""safe"":false},{""name"":""f45"",""parameters"":[],""returntype"":""Void"",""offset"":5604,""safe"":false},{""name"":""f46"",""parameters"":[],""returntype"":""Void"",""offset"":5625,""safe"":false},{""name"":""f47"",""parameters"":[],""returntype"":""Void"",""offset"":5646,""safe"":false},{""name"":""f48"",""parameters"":[],""returntype"":""Void"",""offset"":5667,""safe"":false},{""name"":""f49"",""parameters"":[],""returntype"":""Void"",""offset"":5688,""safe"":false},{""name"":""f50"",""parameters"":[],""returntype"":""Void"",""offset"":5709,""safe"":false},{""name"":""g"",""parameters"":[{""name"":""amountIn"",""type"":""Integer""}],""returntype"":""Void"",""offset"":5730,""safe"":false},{""name"":""g0"",""parameters"":[],""returntype"":""Integer"",""offset"":5749,""safe"":false},{""name"":""g10"",""parameters"":[],""returntype"":""Void"",""offset"":5773,""safe"":false},{""name"":""g11"",""parameters"":[],""returntype"":""Void"",""offset"":5793,""safe"":false},{""name"":""g12"",""parameters"":[],""returntype"":""Void"",""offset"":5813,""safe"":false},{""name"":""g13"",""parameters"":[],""returntype"":""Void"",""offset"":5833,""safe"":false},{""name"":""g14"",""parameters"":[],""returntype"":""Void"",""offset"":5853,""safe"":false},{""name"":""g15"",""parameters"":[],""returntype"":""Void"",""offset"":5873,""safe"":false},{""name"":""g16"",""parameters"":[],""returntype"":""Void"",""offset"":5893,""safe"":false},{""name"":""g17"",""parameters"":[],""returntype"":""Void"",""offset"":5913,""safe"":false},{""name"":""g18"",""parameters"":[],""returntype"":""Void"",""offset"":5934,""safe"":false},{""name"":""g19"",""parameters"":[],""returntype"":""Void"",""offset"":5955,""safe"":false},{""name"":""g20"",""parameters"":[],""returntype"":""Void"",""offset"":5976,""safe"":false},{""name"":""g21"",""parameters"":[],""returntype"":""Void"",""offset"":5997,""safe"":false},{""name"":""g22"",""parameters"":[],""returntype"":""Void"",""offset"":6018,""safe"":false},{""name"":""g23"",""parameters"":[],""returntype"":""Void"",""offset"":6039,""safe"":false},{""name"":""g24"",""parameters"":[],""returntype"":""Void"",""offset"":6060,""safe"":false},{""name"":""g25"",""parameters"":[],""returntype"":""Void"",""offset"":6081,""safe"":false},{""name"":""g26"",""parameters"":[],""returntype"":""Void"",""offset"":6102,""safe"":false},{""name"":""g27"",""parameters"":[],""returntype"":""Void"",""offset"":6123,""safe"":false},{""name"":""g28"",""parameters"":[],""returntype"":""Void"",""offset"":6144,""safe"":false},{""name"":""g29"",""parameters"":[],""returntype"":""Void"",""offset"":6165,""safe"":false},{""name"":""g30"",""parameters"":[],""returntype"":""Void"",""offset"":6186,""safe"":false},{""name"":""g31"",""parameters"":[],""returntype"":""Void"",""offset"":6207,""safe"":false},{""name"":""g32"",""parameters"":[],""returntype"":""Void"",""offset"":6228,""safe"":false},{""name"":""g33"",""parameters"":[],""returntype"":""Void"",""offset"":6249,""safe"":false},{""name"":""g34"",""parameters"":[],""returntype"":""Void"",""offset"":6270,""safe"":false},{""name"":""g35"",""parameters"":[],""returntype"":""Void"",""offset"":6291,""safe"":false},{""name"":""g36"",""parameters"":[],""returntype"":""Void"",""offset"":6312,""safe"":false},{""name"":""g37"",""parameters"":[],""returntype"":""Void"",""offset"":6333,""safe"":false},{""name"":""g38"",""parameters"":[],""returntype"":""Void"",""offset"":6354,""safe"":false},{""name"":""g39"",""parameters"":[],""returntype"":""Void"",""offset"":6375,""safe"":false},{""name"":""g40"",""parameters"":[],""returntype"":""Void"",""offset"":6396,""safe"":false},{""name"":""g41"",""parameters"":[],""returntype"":""Void"",""offset"":6417,""safe"":false},{""name"":""g42"",""parameters"":[],""returntype"":""Void"",""offset"":6438,""safe"":false},{""name"":""g43"",""parameters"":[],""returntype"":""Void"",""offset"":6459,""safe"":false},{""name"":""g44"",""parameters"":[],""returntype"":""Void"",""offset"":6480,""safe"":false},{""name"":""g45"",""parameters"":[],""returntype"":""Void"",""offset"":6501,""safe"":false},{""name"":""g46"",""parameters"":[],""returntype"":""Void"",""offset"":6522,""safe"":false},{""name"":""g47"",""parameters"":[],""returntype"":""Void"",""offset"":6543,""safe"":false},{""name"":""g48"",""parameters"":[],""returntype"":""Void"",""offset"":6564,""safe"":false},{""name"":""g49"",""parameters"":[],""returntype"":""Void"",""offset"":6585,""safe"":false},{""name"":""g50"",""parameters"":[],""returntype"":""Void"",""offset"":6606,""safe"":false},{""name"":""h"",""parameters"":[{""name"":""amountIn"",""type"":""Integer""}],""returntype"":""Void"",""offset"":6627,""safe"":false},{""name"":""h0"",""parameters"":[],""returntype"":""Integer"",""offset"":6646,""safe"":false},{""name"":""h10"",""parameters"":[],""returntype"":""Void"",""offset"":6670,""safe"":false},{""name"":""h11"",""parameters"":[],""returntype"":""Void"",""offset"":6690,""safe"":false},{""name"":""h12"",""parameters"":[],""returntype"":""Void"",""offset"":6710,""safe"":false},{""name"":""h13"",""parameters"":[],""returntype"":""Void"",""offset"":6730,""safe"":false},{""name"":""h14"",""parameters"":[],""returntype"":""Void"",""offset"":6750,""safe"":false},{""name"":""h15"",""parameters"":[],""returntype"":""Void"",""offset"":6770,""safe"":false},{""name"":""h16"",""parameters"":[],""returntype"":""Void"",""offset"":6790,""safe"":false},{""name"":""h17"",""parameters"":[],""returntype"":""Void"",""offset"":6810,""safe"":false},{""name"":""h18"",""parameters"":[],""returntype"":""Void"",""offset"":6831,""safe"":false},{""name"":""h19"",""parameters"":[],""returntype"":""Void"",""offset"":6852,""safe"":false},{""name"":""h20"",""parameters"":[],""returntype"":""Void"",""offset"":6873,""safe"":false},{""name"":""h21"",""parameters"":[],""returntype"":""Void"",""offset"":6894,""safe"":false},{""name"":""h22"",""parameters"":[],""returntype"":""Void"",""offset"":6915,""safe"":false},{""name"":""h23"",""parameters"":[],""returntype"":""Void"",""offset"":6936,""safe"":false},{""name"":""h24"",""parameters"":[],""returntype"":""Void"",""offset"":6957,""safe"":false},{""name"":""h25"",""parameters"":[],""returntype"":""Void"",""offset"":6978,""safe"":false},{""name"":""h26"",""parameters"":[],""returntype"":""Void"",""offset"":6999,""safe"":false},{""name"":""h27"",""parameters"":[],""returntype"":""Void"",""offset"":7020,""safe"":false},{""name"":""h28"",""parameters"":[],""returntype"":""Void"",""offset"":7041,""safe"":false},{""name"":""h29"",""parameters"":[],""returntype"":""Void"",""offset"":7062,""safe"":false},{""name"":""h30"",""parameters"":[],""returntype"":""Void"",""offset"":7083,""safe"":false},{""name"":""h31"",""parameters"":[],""returntype"":""Void"",""offset"":7104,""safe"":false},{""name"":""h32"",""parameters"":[],""returntype"":""Void"",""offset"":7125,""safe"":false},{""name"":""h33"",""parameters"":[],""returntype"":""Void"",""offset"":7146,""safe"":false},{""name"":""h34"",""parameters"":[],""returntype"":""Void"",""offset"":7167,""safe"":false},{""name"":""h35"",""parameters"":[],""returntype"":""Void"",""offset"":7188,""safe"":false},{""name"":""h36"",""parameters"":[],""returntype"":""Void"",""offset"":7209,""safe"":false},{""name"":""h37"",""parameters"":[],""returntype"":""Void"",""offset"":7230,""safe"":false},{""name"":""h38"",""parameters"":[],""returntype"":""Void"",""offset"":7251,""safe"":false},{""name"":""h39"",""parameters"":[],""returntype"":""Void"",""offset"":7272,""safe"":false},{""name"":""h40"",""parameters"":[],""returntype"":""Void"",""offset"":7293,""safe"":false},{""name"":""h41"",""parameters"":[],""returntype"":""Void"",""offset"":7314,""safe"":false},{""name"":""h42"",""parameters"":[],""returntype"":""Void"",""offset"":7335,""safe"":false},{""name"":""h43"",""parameters"":[],""returntype"":""Void"",""offset"":7356,""safe"":false},{""name"":""h44"",""parameters"":[],""returntype"":""Void"",""offset"":7377,""safe"":false},{""name"":""h45"",""parameters"":[],""returntype"":""Void"",""offset"":7398,""safe"":false},{""name"":""h46"",""parameters"":[],""returntype"":""Void"",""offset"":7419,""safe"":false},{""name"":""h47"",""parameters"":[],""returntype"":""Void"",""offset"":7440,""safe"":false},{""name"":""h48"",""parameters"":[],""returntype"":""Void"",""offset"":7461,""safe"":false},{""name"":""h49"",""parameters"":[],""returntype"":""Void"",""offset"":7482,""safe"":false},{""name"":""h50"",""parameters"":[],""returntype"":""Void"",""offset"":7503,""safe"":false},{""name"":""i"",""parameters"":[{""name"":""amountIn"",""type"":""Integer""}],""returntype"":""Void"",""offset"":7524,""safe"":false},{""name"":""i0"",""parameters"":[],""returntype"":""Integer"",""offset"":7545,""safe"":false},{""name"":""i10"",""parameters"":[],""returntype"":""Void"",""offset"":7571,""safe"":false},{""name"":""i11"",""parameters"":[],""returntype"":""Void"",""offset"":7593,""safe"":false},{""name"":""i12"",""parameters"":[],""returntype"":""Void"",""offset"":7615,""safe"":false},{""name"":""i13"",""parameters"":[],""returntype"":""Void"",""offset"":7637,""safe"":false},{""name"":""i14"",""parameters"":[],""returntype"":""Void"",""offset"":7659,""safe"":false},{""name"":""i15"",""parameters"":[],""returntype"":""Void"",""offset"":7681,""safe"":false},{""name"":""i16"",""parameters"":[],""returntype"":""Void"",""offset"":7703,""safe"":false},{""name"":""i17"",""parameters"":[],""returntype"":""Void"",""offset"":7725,""safe"":false},{""name"":""i18"",""parameters"":[],""returntype"":""Void"",""offset"":7748,""safe"":false},{""name"":""i19"",""parameters"":[],""returntype"":""Void"",""offset"":7771,""safe"":false},{""name"":""i20"",""parameters"":[],""returntype"":""Void"",""offset"":7794,""safe"":false},{""name"":""i21"",""parameters"":[],""returntype"":""Void"",""offset"":7817,""safe"":false},{""name"":""i22"",""parameters"":[],""returntype"":""Void"",""offset"":7840,""safe"":false},{""name"":""i23"",""parameters"":[],""returntype"":""Void"",""offset"":7863,""safe"":false},{""name"":""i24"",""parameters"":[],""returntype"":""Void"",""offset"":7886,""safe"":false},{""name"":""i25"",""parameters"":[],""returntype"":""Void"",""offset"":7909,""safe"":false},{""name"":""i26"",""parameters"":[],""returntype"":""Void"",""offset"":7932,""safe"":false},{""name"":""i27"",""parameters"":[],""returntype"":""Void"",""offset"":7955,""safe"":false},{""name"":""i28"",""parameters"":[],""returntype"":""Void"",""offset"":7978,""safe"":false},{""name"":""i29"",""parameters"":[],""returntype"":""Void"",""offset"":8001,""safe"":false},{""name"":""i30"",""parameters"":[],""returntype"":""Void"",""offset"":8024,""safe"":false},{""name"":""i31"",""parameters"":[],""returntype"":""Void"",""offset"":8047,""safe"":false},{""name"":""i32"",""parameters"":[],""returntype"":""Void"",""offset"":8070,""safe"":false},{""name"":""i33"",""parameters"":[],""returntype"":""Void"",""offset"":8093,""safe"":false},{""name"":""i34"",""parameters"":[],""returntype"":""Void"",""offset"":8116,""safe"":false},{""name"":""i35"",""parameters"":[],""returntype"":""Void"",""offset"":8139,""safe"":false},{""name"":""i36"",""parameters"":[],""returntype"":""Void"",""offset"":8162,""safe"":false},{""name"":""i37"",""parameters"":[],""returntype"":""Void"",""offset"":8185,""safe"":false},{""name"":""i38"",""parameters"":[],""returntype"":""Void"",""offset"":8208,""safe"":false},{""name"":""i39"",""parameters"":[],""returntype"":""Void"",""offset"":8231,""safe"":false},{""name"":""i40"",""parameters"":[],""returntype"":""Void"",""offset"":8254,""safe"":false},{""name"":""i41"",""parameters"":[],""returntype"":""Void"",""offset"":8277,""safe"":false},{""name"":""i42"",""parameters"":[],""returntype"":""Void"",""offset"":8300,""safe"":false},{""name"":""i43"",""parameters"":[],""returntype"":""Void"",""offset"":8323,""safe"":false},{""name"":""i44"",""parameters"":[],""returntype"":""Void"",""offset"":8346,""safe"":false},{""name"":""i45"",""parameters"":[],""returntype"":""Void"",""offset"":8369,""safe"":false},{""name"":""i46"",""parameters"":[],""returntype"":""Void"",""offset"":8392,""safe"":false},{""name"":""i47"",""parameters"":[],""returntype"":""Void"",""offset"":8415,""safe"":false},{""name"":""i48"",""parameters"":[],""returntype"":""Void"",""offset"":8438,""safe"":false},{""name"":""i49"",""parameters"":[],""returntype"":""Void"",""offset"":8461,""safe"":false},{""name"":""i50"",""parameters"":[],""returntype"":""Void"",""offset"":8484,""safe"":false},{""name"":""update"",""parameters"":[{""name"":""nefFile"",""type"":""ByteArray""},{""name"":""manifest"",""type"":""String""}],""returntype"":""Void"",""offset"":8511,""safe"":false},{""name"":""j"",""parameters"":[{""name"":""amountIn"",""type"":""Integer""}],""returntype"":""Void"",""offset"":8578,""safe"":false},{""name"":""j0"",""parameters"":[],""returntype"":""Integer"",""offset"":8599,""safe"":false},{""name"":""j10"",""parameters"":[],""returntype"":""Void"",""offset"":8625,""safe"":false},{""name"":""j11"",""parameters"":[],""returntype"":""Void"",""offset"":8647,""safe"":false},{""name"":""j12"",""parameters"":[],""returntype"":""Void"",""offset"":8669,""safe"":false},{""name"":""j13"",""parameters"":[],""returntype"":""Void"",""offset"":8691,""safe"":false},{""name"":""j14"",""parameters"":[],""returntype"":""Void"",""offset"":8713,""safe"":false},{""name"":""j15"",""parameters"":[],""returntype"":""Void"",""offset"":8735,""safe"":false},{""name"":""j16"",""parameters"":[],""returntype"":""Void"",""offset"":8757,""safe"":false},{""name"":""j17"",""parameters"":[],""returntype"":""Void"",""offset"":8779,""safe"":false},{""name"":""j18"",""parameters"":[],""returntype"":""Void"",""offset"":8802,""safe"":false},{""name"":""j19"",""parameters"":[],""returntype"":""Void"",""offset"":8825,""safe"":false},{""name"":""j20"",""parameters"":[],""returntype"":""Void"",""offset"":8848,""safe"":false},{""name"":""j21"",""parameters"":[],""returntype"":""Void"",""offset"":8871,""safe"":false},{""name"":""j22"",""parameters"":[],""returntype"":""Void"",""offset"":8894,""safe"":false},{""name"":""j23"",""parameters"":[],""returntype"":""Void"",""offset"":8917,""safe"":false},{""name"":""j24"",""parameters"":[],""returntype"":""Void"",""offset"":8940,""safe"":false},{""name"":""j25"",""parameters"":[],""returntype"":""Void"",""offset"":8963,""safe"":false},{""name"":""j26"",""parameters"":[],""returntype"":""Void"",""offset"":8986,""safe"":false},{""name"":""j27"",""parameters"":[],""returntype"":""Void"",""offset"":9009,""safe"":false},{""name"":""j28"",""parameters"":[],""returntype"":""Void"",""offset"":9032,""safe"":false},{""name"":""j29"",""parameters"":[],""returntype"":""Void"",""offset"":9055,""safe"":false},{""name"":""j30"",""parameters"":[],""returntype"":""Void"",""offset"":9078,""safe"":false},{""name"":""j31"",""parameters"":[],""returntype"":""Void"",""offset"":9101,""safe"":false},{""name"":""j32"",""parameters"":[],""returntype"":""Void"",""offset"":9124,""safe"":false},{""name"":""j33"",""parameters"":[],""returntype"":""Void"",""offset"":9147,""safe"":false},{""name"":""j34"",""parameters"":[],""returntype"":""Void"",""offset"":9170,""safe"":false},{""name"":""j35"",""parameters"":[],""returntype"":""Void"",""offset"":9193,""safe"":false},{""name"":""j36"",""parameters"":[],""returntype"":""Void"",""offset"":9216,""safe"":false},{""name"":""j37"",""parameters"":[],""returntype"":""Void"",""offset"":9239,""safe"":false},{""name"":""j38"",""parameters"":[],""returntype"":""Void"",""offset"":9262,""safe"":false},{""name"":""j39"",""parameters"":[],""returntype"":""Void"",""offset"":9285,""safe"":false},{""name"":""j40"",""parameters"":[],""returntype"":""Void"",""offset"":9308,""safe"":false},{""name"":""j41"",""parameters"":[],""returntype"":""Void"",""offset"":9331,""safe"":false},{""name"":""j42"",""parameters"":[],""returntype"":""Void"",""offset"":9354,""safe"":false},{""name"":""j43"",""parameters"":[],""returntype"":""Void"",""offset"":9377,""safe"":false},{""name"":""j44"",""parameters"":[],""returntype"":""Void"",""offset"":9400,""safe"":false},{""name"":""j45"",""parameters"":[],""returntype"":""Void"",""offset"":9423,""safe"":false},{""name"":""j46"",""parameters"":[],""returntype"":""Void"",""offset"":9446,""safe"":false},{""name"":""j47"",""parameters"":[],""returntype"":""Void"",""offset"":9469,""safe"":false},{""name"":""j48"",""parameters"":[],""returntype"":""Void"",""offset"":9492,""safe"":false},{""name"":""j49"",""parameters"":[],""returntype"":""Void"",""offset"":9515,""safe"":false},{""name"":""j50"",""parameters"":[],""returntype"":""Void"",""offset"":9538,""safe"":false},{""name"":""k"",""parameters"":[{""name"":""amountIn"",""type"":""Integer""}],""returntype"":""Void"",""offset"":9561,""safe"":false},{""name"":""k0"",""parameters"":[],""returntype"":""Integer"",""offset"":9601,""safe"":false},{""name"":""k10"",""parameters"":[],""returntype"":""Void"",""offset"":9646,""safe"":false},{""name"":""k11"",""parameters"":[],""returntype"":""Void"",""offset"":9687,""safe"":false},{""name"":""k12"",""parameters"":[],""returntype"":""Void"",""offset"":9728,""safe"":false},{""name"":""k13"",""parameters"":[],""returntype"":""Void"",""offset"":9769,""safe"":false},{""name"":""k14"",""parameters"":[],""returntype"":""Void"",""offset"":9810,""safe"":false},{""name"":""k15"",""parameters"":[],""returntype"":""Void"",""offset"":9851,""safe"":false},{""name"":""k16"",""parameters"":[],""returntype"":""Void"",""offset"":9892,""safe"":false},{""name"":""k17"",""parameters"":[],""returntype"":""Void"",""offset"":9933,""safe"":false},{""name"":""k18"",""parameters"":[],""returntype"":""Void"",""offset"":9975,""safe"":false},{""name"":""k19"",""parameters"":[],""returntype"":""Void"",""offset"":10017,""safe"":false},{""name"":""k20"",""parameters"":[],""returntype"":""Void"",""offset"":10059,""safe"":false},{""name"":""k21"",""parameters"":[],""returntype"":""Void"",""offset"":10101,""safe"":false},{""name"":""k22"",""parameters"":[],""returntype"":""Void"",""offset"":10143,""safe"":false},{""name"":""k23"",""parameters"":[],""returntype"":""Void"",""offset"":10185,""safe"":false},{""name"":""k24"",""parameters"":[],""returntype"":""Void"",""offset"":10227,""safe"":false},{""name"":""k25"",""parameters"":[],""returntype"":""Void"",""offset"":10269,""safe"":false},{""name"":""k26"",""parameters"":[],""returntype"":""Void"",""offset"":10311,""safe"":false},{""name"":""k27"",""parameters"":[],""returntype"":""Void"",""offset"":10353,""safe"":false},{""name"":""k28"",""parameters"":[],""returntype"":""Void"",""offset"":10395,""safe"":false},{""name"":""k29"",""parameters"":[],""returntype"":""Void"",""offset"":10437,""safe"":false},{""name"":""k30"",""parameters"":[],""returntype"":""Void"",""offset"":10479,""safe"":false},{""name"":""k31"",""parameters"":[],""returntype"":""Void"",""offset"":10521,""safe"":false},{""name"":""k32"",""parameters"":[],""returntype"":""Void"",""offset"":10563,""safe"":false},{""name"":""k33"",""parameters"":[],""returntype"":""Void"",""offset"":10605,""safe"":false},{""name"":""k34"",""parameters"":[],""returntype"":""Void"",""offset"":10647,""safe"":false},{""name"":""k35"",""parameters"":[],""returntype"":""Void"",""offset"":10689,""safe"":false},{""name"":""k36"",""parameters"":[],""returntype"":""Void"",""offset"":10731,""safe"":false},{""name"":""k37"",""parameters"":[],""returntype"":""Void"",""offset"":10773,""safe"":false},{""name"":""k38"",""parameters"":[],""returntype"":""Void"",""offset"":10815,""safe"":false},{""name"":""k39"",""parameters"":[],""returntype"":""Void"",""offset"":10857,""safe"":false},{""name"":""k40"",""parameters"":[],""returntype"":""Void"",""offset"":10899,""safe"":false},{""name"":""k41"",""parameters"":[],""returntype"":""Void"",""offset"":10941,""safe"":false},{""name"":""k42"",""parameters"":[],""returntype"":""Void"",""offset"":10983,""safe"":false},{""name"":""k43"",""parameters"":[],""returntype"":""Void"",""offset"":11025,""safe"":false},{""name"":""k44"",""parameters"":[],""returntype"":""Void"",""offset"":11067,""safe"":false},{""name"":""k45"",""parameters"":[],""returntype"":""Void"",""offset"":11109,""safe"":false},{""name"":""k46"",""parameters"":[],""returntype"":""Void"",""offset"":11151,""safe"":false},{""name"":""k47"",""parameters"":[],""returntype"":""Void"",""offset"":11193,""safe"":false},{""name"":""k48"",""parameters"":[],""returntype"":""Void"",""offset"":11235,""safe"":false},{""name"":""k49"",""parameters"":[],""returntype"":""Void"",""offset"":11277,""safe"":false},{""name"":""k50"",""parameters"":[],""returntype"":""Void"",""offset"":11319,""safe"":false},{""name"":""l"",""parameters"":[{""name"":""amountIn"",""type"":""Integer""}],""returntype"":""Void"",""offset"":11361,""safe"":false},{""name"":""l0"",""parameters"":[],""returntype"":""Integer"",""offset"":11401,""safe"":false},{""name"":""l10"",""parameters"":[],""returntype"":""Void"",""offset"":11446,""safe"":false},{""name"":""l11"",""parameters"":[],""returntype"":""Void"",""offset"":11487,""safe"":false},{""name"":""l12"",""parameters"":[],""returntype"":""Void"",""offset"":11528,""safe"":false},{""name"":""l13"",""parameters"":[],""returntype"":""Void"",""offset"":11569,""safe"":false},{""name"":""l14"",""parameters"":[],""returntype"":""Void"",""offset"":11610,""safe"":false},{""name"":""l15"",""parameters"":[],""returntype"":""Void"",""offset"":11651,""safe"":false},{""name"":""l16"",""parameters"":[],""returntype"":""Void"",""offset"":11692,""safe"":false},{""name"":""l17"",""parameters"":[],""returntype"":""Void"",""offset"":11733,""safe"":false},{""name"":""l18"",""parameters"":[],""returntype"":""Void"",""offset"":11775,""safe"":false},{""name"":""l19"",""parameters"":[],""returntype"":""Void"",""offset"":11817,""safe"":false},{""name"":""l20"",""parameters"":[],""returntype"":""Void"",""offset"":11859,""safe"":false},{""name"":""l21"",""parameters"":[],""returntype"":""Void"",""offset"":11901,""safe"":false},{""name"":""l22"",""parameters"":[],""returntype"":""Void"",""offset"":11943,""safe"":false},{""name"":""l23"",""parameters"":[],""returntype"":""Void"",""offset"":11985,""safe"":false},{""name"":""l24"",""parameters"":[],""returntype"":""Void"",""offset"":12027,""safe"":false},{""name"":""l25"",""parameters"":[],""returntype"":""Void"",""offset"":12069,""safe"":false},{""name"":""l26"",""parameters"":[],""returntype"":""Void"",""offset"":12111,""safe"":false},{""name"":""l27"",""parameters"":[],""returntype"":""Void"",""offset"":12153,""safe"":false},{""name"":""l28"",""parameters"":[],""returntype"":""Void"",""offset"":12195,""safe"":false},{""name"":""l29"",""parameters"":[],""returntype"":""Void"",""offset"":12237,""safe"":false},{""name"":""l30"",""parameters"":[],""returntype"":""Void"",""offset"":12279,""safe"":false},{""name"":""l31"",""parameters"":[],""returntype"":""Void"",""offset"":12321,""safe"":false},{""name"":""l32"",""parameters"":[],""returntype"":""Void"",""offset"":12363,""safe"":false},{""name"":""l33"",""parameters"":[],""returntype"":""Void"",""offset"":12405,""safe"":false},{""name"":""l34"",""parameters"":[],""returntype"":""Void"",""offset"":12447,""safe"":false},{""name"":""l35"",""parameters"":[],""returntype"":""Void"",""offset"":12489,""safe"":false},{""name"":""l36"",""parameters"":[],""returntype"":""Void"",""offset"":12531,""safe"":false},{""name"":""l37"",""parameters"":[],""returntype"":""Void"",""offset"":12573,""safe"":false},{""name"":""l38"",""parameters"":[],""returntype"":""Void"",""offset"":12615,""safe"":false},{""name"":""l39"",""parameters"":[],""returntype"":""Void"",""offset"":12657,""safe"":false},{""name"":""l40"",""parameters"":[],""returntype"":""Void"",""offset"":12699,""safe"":false},{""name"":""l41"",""parameters"":[],""returntype"":""Void"",""offset"":12741,""safe"":false},{""name"":""l42"",""parameters"":[],""returntype"":""Void"",""offset"":12783,""safe"":false},{""name"":""l43"",""parameters"":[],""returntype"":""Void"",""offset"":12825,""safe"":false},{""name"":""l44"",""parameters"":[],""returntype"":""Void"",""offset"":12867,""safe"":false},{""name"":""l45"",""parameters"":[],""returntype"":""Void"",""offset"":12909,""safe"":false},{""name"":""l46"",""parameters"":[],""returntype"":""Void"",""offset"":12951,""safe"":false},{""name"":""l47"",""parameters"":[],""returntype"":""Void"",""offset"":12993,""safe"":false},{""name"":""l48"",""parameters"":[],""returntype"":""Void"",""offset"":13035,""safe"":false},{""name"":""l49"",""parameters"":[],""returntype"":""Void"",""offset"":13077,""safe"":false},{""name"":""l50"",""parameters"":[],""returntype"":""Void"",""offset"":13119,""safe"":false},{""name"":""m"",""parameters"":[{""name"":""amountIn"",""type"":""Integer""}],""returntype"":""Void"",""offset"":13161,""safe"":false},{""name"":""m0"",""parameters"":[],""returntype"":""Integer"",""offset"":13181,""safe"":false},{""name"":""m10"",""parameters"":[],""returntype"":""Void"",""offset"":13206,""safe"":false},{""name"":""m11"",""parameters"":[],""returntype"":""Void"",""offset"":13227,""safe"":false},{""name"":""m12"",""parameters"":[],""returntype"":""Void"",""offset"":13248,""safe"":false},{""name"":""m13"",""parameters"":[],""returntype"":""Void"",""offset"":13269,""safe"":false},{""name"":""m14"",""parameters"":[],""returntype"":""Void"",""offset"":13290,""safe"":false},{""name"":""m15"",""parameters"":[],""returntype"":""Void"",""offset"":13311,""safe"":false},{""name"":""m16"",""parameters"":[],""returntype"":""Void"",""offset"":13332,""safe"":false},{""name"":""m17"",""parameters"":[],""returntype"":""Void"",""offset"":13353,""safe"":false},{""name"":""m18"",""parameters"":[],""returntype"":""Void"",""offset"":13375,""safe"":false},{""name"":""m19"",""parameters"":[],""returntype"":""Void"",""offset"":13397,""safe"":false},{""name"":""m20"",""parameters"":[],""returntype"":""Void"",""offset"":13419,""safe"":false},{""name"":""m21"",""parameters"":[],""returntype"":""Void"",""offset"":13441,""safe"":false},{""name"":""m22"",""parameters"":[],""returntype"":""Void"",""offset"":13463,""safe"":false},{""name"":""m23"",""parameters"":[],""returntype"":""Void"",""offset"":13485,""safe"":false},{""name"":""m24"",""parameters"":[],""returntype"":""Void"",""offset"":13507,""safe"":false},{""name"":""m25"",""parameters"":[],""returntype"":""Void"",""offset"":13529,""safe"":false},{""name"":""m26"",""parameters"":[],""returntype"":""Void"",""offset"":13551,""safe"":false},{""name"":""m27"",""parameters"":[],""returntype"":""Void"",""offset"":13573,""safe"":false},{""name"":""m28"",""parameters"":[],""returntype"":""Void"",""offset"":13595,""safe"":false},{""name"":""m29"",""parameters"":[],""returntype"":""Void"",""offset"":13617,""safe"":false},{""name"":""m30"",""parameters"":[],""returntype"":""Void"",""offset"":13639,""safe"":false},{""name"":""m31"",""parameters"":[],""returntype"":""Void"",""offset"":13661,""safe"":false},{""name"":""m32"",""parameters"":[],""returntype"":""Void"",""offset"":13683,""safe"":false},{""name"":""m33"",""parameters"":[],""returntype"":""Void"",""offset"":13705,""safe"":false},{""name"":""m34"",""parameters"":[],""returntype"":""Void"",""offset"":13727,""safe"":false},{""name"":""m35"",""parameters"":[],""returntype"":""Void"",""offset"":13749,""safe"":false},{""name"":""m36"",""parameters"":[],""returntype"":""Void"",""offset"":13771,""safe"":false},{""name"":""m37"",""parameters"":[],""returntype"":""Void"",""offset"":13793,""safe"":false},{""name"":""m38"",""parameters"":[],""returntype"":""Void"",""offset"":13815,""safe"":false},{""name"":""m39"",""parameters"":[],""returntype"":""Void"",""offset"":13837,""safe"":false},{""name"":""m40"",""parameters"":[],""returntype"":""Void"",""offset"":13859,""safe"":false},{""name"":""m41"",""parameters"":[],""returntype"":""Void"",""offset"":13881,""safe"":false},{""name"":""m42"",""parameters"":[],""returntype"":""Void"",""offset"":13903,""safe"":false},{""name"":""m43"",""parameters"":[],""returntype"":""Void"",""offset"":13925,""safe"":false},{""name"":""m44"",""parameters"":[],""returntype"":""Void"",""offset"":13947,""safe"":false},{""name"":""m45"",""parameters"":[],""returntype"":""Void"",""offset"":13969,""safe"":false},{""name"":""m46"",""parameters"":[],""returntype"":""Void"",""offset"":13991,""safe"":false},{""name"":""m47"",""parameters"":[],""returntype"":""Void"",""offset"":14013,""safe"":false},{""name"":""m48"",""parameters"":[],""returntype"":""Void"",""offset"":14035,""safe"":false},{""name"":""m49"",""parameters"":[],""returntype"":""Void"",""offset"":14057,""safe"":false},{""name"":""m50"",""parameters"":[],""returntype"":""Void"",""offset"":14079,""safe"":false},{""name"":""n"",""parameters"":[{""name"":""amountIn"",""type"":""Integer""}],""returntype"":""Void"",""offset"":14101,""safe"":false},{""name"":""n0"",""parameters"":[],""returntype"":""Integer"",""offset"":14121,""safe"":false},{""name"":""n10"",""parameters"":[],""returntype"":""Void"",""offset"":14146,""safe"":false},{""name"":""n11"",""parameters"":[],""returntype"":""Void"",""offset"":14167,""safe"":false},{""name"":""n12"",""parameters"":[],""returntype"":""Void"",""offset"":14188,""safe"":false},{""name"":""n13"",""parameters"":[],""returntype"":""Void"",""offset"":14209,""safe"":false},{""name"":""n14"",""parameters"":[],""returntype"":""Void"",""offset"":14230,""safe"":false},{""name"":""n15"",""parameters"":[],""returntype"":""Void"",""offset"":14251,""safe"":false},{""name"":""n16"",""parameters"":[],""returntype"":""Void"",""offset"":14272,""safe"":false},{""name"":""n17"",""parameters"":[],""returntype"":""Void"",""offset"":14293,""safe"":false},{""name"":""n18"",""parameters"":[],""returntype"":""Void"",""offset"":14315,""safe"":false},{""name"":""n19"",""parameters"":[],""returntype"":""Void"",""offset"":14337,""safe"":false},{""name"":""n20"",""parameters"":[],""returntype"":""Void"",""offset"":14359,""safe"":false},{""name"":""n21"",""parameters"":[],""returntype"":""Void"",""offset"":14381,""safe"":false},{""name"":""n22"",""parameters"":[],""returntype"":""Void"",""offset"":14403,""safe"":false},{""name"":""n23"",""parameters"":[],""returntype"":""Void"",""offset"":14425,""safe"":false},{""name"":""n24"",""parameters"":[],""returntype"":""Void"",""offset"":14447,""safe"":false},{""name"":""n25"",""parameters"":[],""returntype"":""Void"",""offset"":14469,""safe"":false},{""name"":""n26"",""parameters"":[],""returntype"":""Void"",""offset"":14491,""safe"":false},{""name"":""n27"",""parameters"":[],""returntype"":""Void"",""offset"":14513,""safe"":false},{""name"":""n28"",""parameters"":[],""returntype"":""Void"",""offset"":14535,""safe"":false},{""name"":""n29"",""parameters"":[],""returntype"":""Void"",""offset"":14557,""safe"":false},{""name"":""n30"",""parameters"":[],""returntype"":""Void"",""offset"":14579,""safe"":false},{""name"":""n31"",""parameters"":[],""returntype"":""Void"",""offset"":14601,""safe"":false},{""name"":""n32"",""parameters"":[],""returntype"":""Void"",""offset"":14623,""safe"":false},{""name"":""n33"",""parameters"":[],""returntype"":""Void"",""offset"":14645,""safe"":false},{""name"":""n34"",""parameters"":[],""returntype"":""Void"",""offset"":14667,""safe"":false},{""name"":""n35"",""parameters"":[],""returntype"":""Void"",""offset"":14689,""safe"":false},{""name"":""n36"",""parameters"":[],""returntype"":""Void"",""offset"":14711,""safe"":false},{""name"":""n37"",""parameters"":[],""returntype"":""Void"",""offset"":14733,""safe"":false},{""name"":""n38"",""parameters"":[],""returntype"":""Void"",""offset"":14755,""safe"":false},{""name"":""n39"",""parameters"":[],""returntype"":""Void"",""offset"":14777,""safe"":false},{""name"":""n40"",""parameters"":[],""returntype"":""Void"",""offset"":14799,""safe"":false},{""name"":""n41"",""parameters"":[],""returntype"":""Void"",""offset"":14821,""safe"":false},{""name"":""n42"",""parameters"":[],""returntype"":""Void"",""offset"":14843,""safe"":false},{""name"":""n43"",""parameters"":[],""returntype"":""Void"",""offset"":14865,""safe"":false},{""name"":""n44"",""parameters"":[],""returntype"":""Void"",""offset"":14887,""safe"":false},{""name"":""n45"",""parameters"":[],""returntype"":""Void"",""offset"":14909,""safe"":false},{""name"":""n46"",""parameters"":[],""returntype"":""Void"",""offset"":14931,""safe"":false},{""name"":""n47"",""parameters"":[],""returntype"":""Void"",""offset"":14953,""safe"":false},{""name"":""n48"",""parameters"":[],""returntype"":""Void"",""offset"":14975,""safe"":false},{""name"":""n49"",""parameters"":[],""returntype"":""Void"",""offset"":14997,""safe"":false},{""name"":""n50"",""parameters"":[],""returntype"":""Void"",""offset"":15019,""safe"":false},{""name"":""_initialize"",""parameters"":[],""returntype"":""Void"",""offset"":15041,""safe"":false}],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""extra"":{""Author"":""Test"",""Email"":""Test@Test"",""Description"":""This is a Test Contract""}}"; + var manifest = ContractManifest.Parse(json); + + var counter = new ReferenceCounter(); + var item = manifest.ToStackItem(counter); + var data = BinarySerializer.Serialize(item, 1024 * 1024, 4096); + + Assert.ThrowsException(() => BinarySerializer.Deserialize(data, ExecutionEngineLimits.Default, counter)); + Assert.ThrowsException(() => BinarySerializer.Serialize(item, 1024 * 1024, 2048)); + + item = BinarySerializer.Deserialize(data, ExecutionEngineLimits.Default with { MaxStackSize = 4096 }, counter); + var copy = item.ToInteroperable(); + + Assert.AreEqual(manifest.ToJson().ToString(false), copy.ToJson().ToString(false)); + } + [TestMethod] public void ParseFromJson_Default() { @@ -20,7 +51,7 @@ public void ParseFromJson_Default() Assert.AreEqual(manifest.ToJson().ToString(), json); Assert.AreEqual(manifest.ToJson().ToString(), TestUtils.CreateDefaultManifest().ToJson().ToString()); - Assert.IsTrue(manifest.IsValid(UInt160.Zero)); + Assert.IsTrue(manifest.IsValid(ExecutionEngineLimits.Default, UInt160.Zero)); } [TestMethod] @@ -56,7 +87,6 @@ public void ParseFromJson_SafeMethods() [TestMethod] public void ParseFromJson_Trust() { - ReferenceCounter referenceCounter = new ReferenceCounter(); var json = @"{""name"":""testManifest"",""groups"":[],""features"":{},""supportedstandards"":[],""abi"":{""methods"":[{""name"":""testMethod"",""parameters"":[],""returntype"":""Void"",""offset"":0,""safe"":true}],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[""0x0000000000000000000000000000000000000001"",""*""],""extra"":null}"; var manifest = ContractManifest.Parse(json); Assert.AreEqual(manifest.ToJson().ToString(), json); @@ -65,6 +95,23 @@ public void ParseFromJson_Trust() Assert.AreEqual(manifest.ToJson().ToString(), check.ToJson().ToString()); } + [TestMethod] + public void ToInteroperable_Trust() + { + var json = @"{""name"":""CallOracleContract-6"",""groups"":[],""features"":{},""supportedstandards"":[],""abi"":{""methods"":[{""name"":""request"",""parameters"":[{""name"":""url"",""type"":""String""},{""name"":""filter"",""type"":""String""},{""name"":""gasForResponse"",""type"":""Integer""}],""returntype"":""Void"",""offset"":0,""safe"":false},{""name"":""callback"",""parameters"":[{""name"":""url"",""type"":""String""},{""name"":""userData"",""type"":""Any""},{""name"":""responseCode"",""type"":""Integer""},{""name"":""response"",""type"":""ByteArray""}],""returntype"":""Void"",""offset"":86,""safe"":false},{""name"":""getStoredUrl"",""parameters"":[],""returntype"":""String"",""offset"":129,""safe"":false},{""name"":""getStoredResponseCode"",""parameters"":[],""returntype"":""Integer"",""offset"":142,""safe"":false},{""name"":""getStoredResponse"",""parameters"":[],""returntype"":""ByteArray"",""offset"":165,""safe"":false}],""events"":[]},""permissions"":[{""contract"":""0xfe924b7cfe89ddd271abaf7210a80a7e11178758"",""methods"":""*""},{""contract"":""*"",""methods"":""*""}],""trusts"":[""0xfe924b7cfe89ddd271abaf7210a80a7e11178758"",""*""],""extra"":{}}"; + var manifest = ContractManifest.Parse(json); + var s = (VM.Types.Struct)manifest.ToStackItem(new ReferenceCounter()); + manifest = s.ToInteroperable(); + + Assert.IsFalse(manifest.Permissions[0].Contract.IsWildcard); + Assert.IsTrue(manifest.Permissions[0].Methods.IsWildcard); + Assert.IsTrue(manifest.Permissions[1].Contract.IsWildcard); + Assert.IsTrue(manifest.Permissions[1].Methods.IsWildcard); + + Assert.IsFalse(manifest.Trusts[0].IsWildcard); + Assert.IsTrue(manifest.Trusts[1].IsWildcard); + } + [TestMethod] public void ParseFromJson_Groups() { diff --git a/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractPermission.cs b/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractPermission.cs index 25f3c36720..972a06d56d 100644 --- a/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractPermission.cs +++ b/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractPermission.cs @@ -1,7 +1,19 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ContractPermission.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; using Neo.SmartContract; using Neo.SmartContract.Manifest; +using Neo.VM.Types; using System; namespace Neo.UnitTests.SmartContract.Manifest @@ -9,6 +21,32 @@ namespace Neo.UnitTests.SmartContract.Manifest [TestClass] public class UT_ContractPermission { + [TestMethod] + public void TestDeserialize() + { + // null + ContractPermission contractPermission = ContractPermission.DefaultPermission; + Struct s = (Struct)contractPermission.ToStackItem(new VM.ReferenceCounter()); + + contractPermission = s.ToInteroperable(); + Assert.IsTrue(contractPermission.Contract.IsWildcard); + Assert.IsTrue(contractPermission.Methods.IsWildcard); + + // not null + contractPermission = new ContractPermission() + { + Contract = ContractPermissionDescriptor.Create(UInt160.Zero), + Methods = WildcardContainer.Create("test") + }; + s = (Struct)contractPermission.ToStackItem(new VM.ReferenceCounter()); + + contractPermission = s.ToInteroperable(); + Assert.IsFalse(contractPermission.Contract.IsWildcard); + Assert.IsFalse(contractPermission.Methods.IsWildcard); + Assert.AreEqual(UInt160.Zero, contractPermission.Contract.Hash); + Assert.AreEqual("test", contractPermission.Methods[0]); + } + [TestMethod] public void TestIsAllowed() { diff --git a/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractPermissionDescriptor.cs b/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractPermissionDescriptor.cs index 972911b296..0dee8b0f67 100644 --- a/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractPermissionDescriptor.cs +++ b/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractPermissionDescriptor.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ContractPermissionDescriptor.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.SmartContract.Manifest; using Neo.Wallets; diff --git a/tests/Neo.UnitTests/SmartContract/Manifest/UT_WildCardContainer.cs b/tests/Neo.UnitTests/SmartContract/Manifest/UT_WildCardContainer.cs index 9bd8dc111f..610126485c 100644 --- a/tests/Neo.UnitTests/SmartContract/Manifest/UT_WildCardContainer.cs +++ b/tests/Neo.UnitTests/SmartContract/Manifest/UT_WildCardContainer.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_WildCardContainer.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Json; @@ -59,7 +70,7 @@ public void TestGetItem() public void TestGetEnumerator() { string[] s = null; - IReadOnlyList rs = (IReadOnlyList)new string[0]; + IReadOnlyList rs = new string[0]; WildcardContainer container = WildcardContainer.Create(s); IEnumerator enumerator = container.GetEnumerator(); enumerator.Should().Be(rs.GetEnumerator()); diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_ContractEventAttribute.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_ContractEventAttribute.cs new file mode 100644 index 0000000000..ba8f703253 --- /dev/null +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_ContractEventAttribute.cs @@ -0,0 +1,151 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ContractEventAttribute.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.SmartContract; +using Neo.SmartContract.Native; + +namespace Neo.UnitTests.SmartContract.Native +{ + [TestClass] + public class UT_ContractEventAttribute + { + [TestMethod] + public void TestConstructorOneArg() + { + var arg = new ContractEventAttribute(Hardfork.HF_Basilisk, 0, "1", "a1", ContractParameterType.String); + + Assert.AreEqual(Hardfork.HF_Basilisk, arg.ActiveIn); + Assert.AreEqual(0, arg.Order); + Assert.AreEqual("1", arg.Descriptor.Name); + Assert.AreEqual(1, arg.Descriptor.Parameters.Length); + Assert.AreEqual("a1", arg.Descriptor.Parameters[0].Name); + Assert.AreEqual(ContractParameterType.String, arg.Descriptor.Parameters[0].Type); + + arg = new ContractEventAttribute(1, "1", "a1", ContractParameterType.String); + + Assert.IsNull(arg.ActiveIn); + Assert.AreEqual(1, arg.Order); + Assert.AreEqual("1", arg.Descriptor.Name); + Assert.AreEqual(1, arg.Descriptor.Parameters.Length); + Assert.AreEqual("a1", arg.Descriptor.Parameters[0].Name); + Assert.AreEqual(ContractParameterType.String, arg.Descriptor.Parameters[0].Type); + } + + [TestMethod] + public void TestConstructorTwoArg() + { + var arg = new ContractEventAttribute(Hardfork.HF_Basilisk, 0, "2", + "a1", ContractParameterType.String, + "a2", ContractParameterType.Integer); + + Assert.AreEqual(Hardfork.HF_Basilisk, arg.ActiveIn); + Assert.AreEqual(0, arg.Order); + Assert.AreEqual("2", arg.Descriptor.Name); + Assert.AreEqual(2, arg.Descriptor.Parameters.Length); + Assert.AreEqual("a1", arg.Descriptor.Parameters[0].Name); + Assert.AreEqual(ContractParameterType.String, arg.Descriptor.Parameters[0].Type); + Assert.AreEqual("a2", arg.Descriptor.Parameters[1].Name); + Assert.AreEqual(ContractParameterType.Integer, arg.Descriptor.Parameters[1].Type); + + arg = new ContractEventAttribute(1, "2", + "a1", ContractParameterType.String, + "a2", ContractParameterType.Integer); + + Assert.IsNull(arg.ActiveIn); + Assert.AreEqual(1, arg.Order); + Assert.AreEqual("2", arg.Descriptor.Name); + Assert.AreEqual(2, arg.Descriptor.Parameters.Length); + Assert.AreEqual("a1", arg.Descriptor.Parameters[0].Name); + Assert.AreEqual(ContractParameterType.String, arg.Descriptor.Parameters[0].Type); + Assert.AreEqual("a2", arg.Descriptor.Parameters[1].Name); + Assert.AreEqual(ContractParameterType.Integer, arg.Descriptor.Parameters[1].Type); + } + + [TestMethod] + public void TestConstructorThreeArg() + { + var arg = new ContractEventAttribute(Hardfork.HF_Basilisk, 0, "3", + "a1", ContractParameterType.String, + "a2", ContractParameterType.Integer, + "a3", ContractParameterType.Boolean); + + Assert.AreEqual(Hardfork.HF_Basilisk, arg.ActiveIn); + Assert.AreEqual(0, arg.Order); + Assert.AreEqual("3", arg.Descriptor.Name); + Assert.AreEqual(3, arg.Descriptor.Parameters.Length); + Assert.AreEqual("a1", arg.Descriptor.Parameters[0].Name); + Assert.AreEqual(ContractParameterType.String, arg.Descriptor.Parameters[0].Type); + Assert.AreEqual("a2", arg.Descriptor.Parameters[1].Name); + Assert.AreEqual(ContractParameterType.Integer, arg.Descriptor.Parameters[1].Type); + Assert.AreEqual("a3", arg.Descriptor.Parameters[2].Name); + Assert.AreEqual(ContractParameterType.Boolean, arg.Descriptor.Parameters[2].Type); + + arg = new ContractEventAttribute(1, "3", + "a1", ContractParameterType.String, + "a2", ContractParameterType.Integer, + "a3", ContractParameterType.Boolean); + + Assert.IsNull(arg.ActiveIn); + Assert.AreEqual(1, arg.Order); + Assert.AreEqual("3", arg.Descriptor.Name); + Assert.AreEqual(3, arg.Descriptor.Parameters.Length); + Assert.AreEqual("a1", arg.Descriptor.Parameters[0].Name); + Assert.AreEqual(ContractParameterType.String, arg.Descriptor.Parameters[0].Type); + Assert.AreEqual("a2", arg.Descriptor.Parameters[1].Name); + Assert.AreEqual(ContractParameterType.Integer, arg.Descriptor.Parameters[1].Type); + Assert.AreEqual("a3", arg.Descriptor.Parameters[2].Name); + Assert.AreEqual(ContractParameterType.Boolean, arg.Descriptor.Parameters[2].Type); + } + + [TestMethod] + public void TestConstructorFourArg() + { + var arg = new ContractEventAttribute(Hardfork.HF_Basilisk, 0, "4", + "a1", ContractParameterType.String, + "a2", ContractParameterType.Integer, + "a3", ContractParameterType.Boolean, + "a4", ContractParameterType.Array); + + Assert.AreEqual(Hardfork.HF_Basilisk, arg.ActiveIn); + Assert.AreEqual(0, arg.Order); + Assert.AreEqual("4", arg.Descriptor.Name); + Assert.AreEqual(4, arg.Descriptor.Parameters.Length); + Assert.AreEqual("a1", arg.Descriptor.Parameters[0].Name); + Assert.AreEqual(ContractParameterType.String, arg.Descriptor.Parameters[0].Type); + Assert.AreEqual("a2", arg.Descriptor.Parameters[1].Name); + Assert.AreEqual(ContractParameterType.Integer, arg.Descriptor.Parameters[1].Type); + Assert.AreEqual("a3", arg.Descriptor.Parameters[2].Name); + Assert.AreEqual(ContractParameterType.Boolean, arg.Descriptor.Parameters[2].Type); + Assert.AreEqual("a4", arg.Descriptor.Parameters[3].Name); + Assert.AreEqual(ContractParameterType.Array, arg.Descriptor.Parameters[3].Type); + + arg = new ContractEventAttribute(1, "4", + "a1", ContractParameterType.String, + "a2", ContractParameterType.Integer, + "a3", ContractParameterType.Boolean, + "a4", ContractParameterType.Array); + + Assert.IsNull(arg.ActiveIn); + Assert.AreEqual(1, arg.Order); + Assert.AreEqual("4", arg.Descriptor.Name); + Assert.AreEqual(4, arg.Descriptor.Parameters.Length); + Assert.AreEqual("a1", arg.Descriptor.Parameters[0].Name); + Assert.AreEqual(ContractParameterType.String, arg.Descriptor.Parameters[0].Type); + Assert.AreEqual("a2", arg.Descriptor.Parameters[1].Name); + Assert.AreEqual(ContractParameterType.Integer, arg.Descriptor.Parameters[1].Type); + Assert.AreEqual("a3", arg.Descriptor.Parameters[2].Name); + Assert.AreEqual(ContractParameterType.Boolean, arg.Descriptor.Parameters[2].Type); + Assert.AreEqual("a4", arg.Descriptor.Parameters[3].Name); + Assert.AreEqual(ContractParameterType.Array, arg.Descriptor.Parameters[3].Type); + } + } +} diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_ContractMethodAttribute.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_ContractMethodAttribute.cs new file mode 100644 index 0000000000..a2b746d7c8 --- /dev/null +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_ContractMethodAttribute.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ContractMethodAttribute.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.SmartContract.Native; + +namespace Neo.UnitTests.SmartContract.Native +{ + [TestClass] + public class UT_ContractMethodAttribute + { + [TestMethod] + public void TestConstructorOneArg() + { + var arg = new ContractMethodAttribute(); + + Assert.IsNull(arg.ActiveIn); + + arg = new ContractMethodAttribute(Hardfork.HF_Aspidochelone); + + Assert.AreEqual(Hardfork.HF_Aspidochelone, arg.ActiveIn); + } + } +} diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs index f6e142500a..f65e061093 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs @@ -1,10 +1,30 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_CryptoLib.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography; using Neo.Cryptography.BLS12_381; +using Neo.Cryptography.ECC; +using Neo.IO; +using Neo.Ledger; +using Neo.Network.P2P; +using Neo.Network.P2P.Payloads; using Neo.SmartContract; using Neo.SmartContract.Native; using Neo.VM; -using System.Security.Cryptography; +using Org.BouncyCastle.Utilities.Encoders; +using System; +using System.Collections.Generic; +using System.Linq; namespace Neo.UnitTests.SmartContract.Native { @@ -324,5 +344,566 @@ public void TestBls12381ScalarMul_Compat() BLS12381PointType.G2Proj ); } + + /// + /// Keccak256 cases are verified in https://emn178.github.io/online-tools/keccak_256.html + /// + [TestMethod] + public void TestKeccak256_HelloWorld() + { + // Arrange + byte[] inputData = "Hello, World!"u8.ToArray(); + string expectedHashHex = "acaf3289d7b601cbd114fb36c4d29c85bbfd5e133f14cb355c3fd8d99367964f"; + + // Act + byte[] outputData = CryptoLib.Keccak256(inputData); + string outputHashHex = Hex.ToHexString(outputData); + + // Assert + Assert.AreEqual(expectedHashHex, outputHashHex, "Keccak256 hash did not match expected value for 'Hello, World!'."); + } + [TestMethod] + public void TestKeccak256_Keccak() + { + // Arrange + byte[] inputData = "Keccak"u8.ToArray(); + string expectedHashHex = "868c016b666c7d3698636ee1bd023f3f065621514ab61bf26f062c175fdbe7f2"; + + // Act + byte[] outputData = CryptoLib.Keccak256(inputData); + string outputHashHex = Hex.ToHexString(outputData); + + // Assert + Assert.AreEqual(expectedHashHex, outputHashHex, "Keccak256 hash did not match expected value for 'Keccak'."); + } + + [TestMethod] + public void TestKeccak256_Cryptography() + { + // Arrange + byte[] inputData = "Cryptography"u8.ToArray(); + string expectedHashHex = "53d49d225dd2cfe77d8c5e2112bcc9efe77bea1c7aa5e5ede5798a36e99e2d29"; + + // Act + byte[] outputData = CryptoLib.Keccak256(inputData); + string outputHashHex = Hex.ToHexString(outputData); + + // Assert + Assert.AreEqual(expectedHashHex, outputHashHex, "Keccak256 hash did not match expected value for 'Cryptography'."); + } + + [TestMethod] + public void TestKeccak256_Testing123() + { + // Arrange + byte[] inputData = "Testing123"u8.ToArray(); + string expectedHashHex = "3f82db7b16b0818a1c6b2c6152e265f682d5ebcf497c9aad776ad38bc39cb6ca"; + + // Act + byte[] outputData = CryptoLib.Keccak256(inputData); + string outputHashHex = Hex.ToHexString(outputData); + + // Assert + Assert.AreEqual(expectedHashHex, outputHashHex, "Keccak256 hash did not match expected value for 'Testing123'."); + } + + [TestMethod] + public void TestKeccak256_LongString() + { + // Arrange + byte[] inputData = "This is a longer string for Keccak256 testing purposes."u8.ToArray(); + string expectedHashHex = "24115e5c2359f85f6840b42acd2f7ea47bc239583e576d766fa173bf711bdd2f"; + + // Act + byte[] outputData = CryptoLib.Keccak256(inputData); + string outputHashHex = Hex.ToHexString(outputData); + + // Assert + Assert.AreEqual(expectedHashHex, outputHashHex, "Keccak256 hash did not match expected value for the longer string."); + } + + [TestMethod] + public void TestKeccak256_BlankString() + { + // Arrange + byte[] inputData = ""u8.ToArray(); + string expectedHashHex = "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"; + + // Act + byte[] outputData = CryptoLib.Keccak256(inputData); + string outputHashHex = Hex.ToHexString(outputData); + + // Assert + Assert.AreEqual(expectedHashHex, outputHashHex, "Keccak256 hash did not match expected value for blank string."); + } + + // TestVerifyWithECDsa_CustomTxWitness_SingleSig builds custom witness verification script for single Koblitz public key + // and ensures witness check is passed for the following message: + // + // keccak256([4-bytes-network-magic-LE, txHash-bytes-BE]) + // + // The proposed witness verification script has 110 bytes length, verification costs 2154270 * 10e-8GAS including Invocation script execution. + // The user has to sign the keccak256([4-bytes-network-magic-LE, txHash-bytes-BE]). + [TestMethod] + public void TestVerifyWithECDsa_CustomTxWitness_SingleSig() + { + byte[] privkey = "7177f0d04c79fa0b8c91fe90c1cf1d44772d1fba6e5eb9b281a22cd3aafb51fe".HexToBytes(); + ECPoint pubKey = ECPoint.Parse("04fd0a8c1ce5ae5570fdd46e7599c16b175bf0ebdfe9c178f1ab848fb16dac74a5d301b0534c7bcf1b3760881f0c420d17084907edd771e1c9c8e941bbf6ff9108", ECCurve.Secp256k1); + + // vrf is a builder of witness verification script corresponding to the public key. + using ScriptBuilder vrf = new(); + vrf.EmitPush((byte)NamedCurveHash.secp256k1Keccak256); // push Koblitz curve identifier and Keccak256 hasher. + vrf.Emit(OpCode.SWAP); // swap curve identifier with the signature. + vrf.EmitPush(pubKey.EncodePoint(true)); // emit the caller's public key. + + // Construct and push the signed message. The signed message is effectively the network-dependent transaction hash, + // i.e. msg = [4-network-magic-bytes-LE, tx-hash-BE] + // Firstly, retrieve network magic (it's uint32 wrapped into BigInteger and represented as Integer stackitem on stack). + vrf.EmitSysCall(ApplicationEngine.System_Runtime_GetNetwork); // push network magic (Integer stackitem), can have 0-5 bytes length serialized. + + // Convert network magic to 4-bytes-length LE byte array representation. + vrf.EmitPush(0x100000000); // push 0x100000000. + vrf.Emit(OpCode.ADD, // the result is some new number that is 5 bytes at least when serialized, but first 4 bytes are intact network value (LE). + OpCode.PUSH4, OpCode.LEFT); // cut the first 4 bytes out of a number that is at least 5 bytes long, the result is 4-bytes-length LE network representation. + + // Retrieve executing transaction hash. + vrf.EmitSysCall(ApplicationEngine.System_Runtime_GetScriptContainer); // push the script container (executing transaction, actually). + vrf.Emit(OpCode.PUSH0, OpCode.PICKITEM); // pick 0-th transaction item (the transaction hash). + + // Concatenate network magic and transaction hash. + vrf.Emit(OpCode.CAT); // this instruction will convert network magic to bytes using BigInteger rules of conversion. + + // Continue construction of 'verifyWithECDsa' call. + vrf.Emit(OpCode.PUSH4, OpCode.PACK); // pack arguments for 'verifyWithECDsa' call. + EmitAppCallNoArgs(vrf, CryptoLib.CryptoLib.Hash, "verifyWithECDsa", CallFlags.None); // emit the call to 'verifyWithECDsa' itself. + + // Account is a hash of verification script. + var vrfScript = vrf.ToArray(); + var acc = vrfScript.ToScriptHash(); + + var tx = new Transaction + { + Attributes = [], + NetworkFee = 1_0000_0000, + Nonce = (uint)Environment.TickCount, + Script = new byte[Transaction.MaxTransactionSize / 100], + Signers = [new Signer { Account = acc }], + SystemFee = 0, + ValidUntilBlock = 10, + Version = 0, + Witnesses = [] + }; + var tx_signature = Crypto.Sign(tx.GetSignData(TestBlockchain.TheNeoSystem.Settings.Network), privkey, ECCurve.Secp256k1, Hasher.Keccak256); + + // inv is a builder of witness invocation script corresponding to the public key. + using ScriptBuilder inv = new(); + inv.EmitPush(tx_signature); // push signature. + + tx.Witnesses = + [ + new Witness { InvocationScript = inv.ToArray(), VerificationScript = vrfScript } + ]; + + tx.VerifyStateIndependent(TestProtocolSettings.Default).Should().Be(VerifyResult.Succeed); + + var snapshot = TestBlockchain.GetTestSnapshot(); + + // Create fake balance to pay the fees. + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: long.MaxValue); + _ = NativeContract.GAS.Mint(engine, acc, 5_0000_0000, false); + snapshot.Commit(); + + var txVrfContext = new TransactionVerificationContext(); + var conflicts = new List(); + tx.VerifyStateDependent(TestProtocolSettings.Default, snapshot, txVrfContext, conflicts).Should().Be(VerifyResult.Succeed); + + // The resulting witness verification cost is 2154270 * 10e-8GAS. + // The resulting witness Invocation script (66 bytes length): + // NEO-VM > loadbase64 DEARoaaEjM/3VulrBDUod7eiZgWQS2iXIM0+I24iyJYmffhosZoQjfnnRymF/7+FaBPb9qvQwxLLSVo9ROlrdFdC + // READY: loaded 66 instructions + // NEO-VM 0 > ops + // INDEX OPCODE PARAMETER + // 0 PUSHDATA1 11a1a6848ccff756e96b04352877b7a26605904b689720cd3e236e22c896267df868b19a108df9e7472985ffbf856813dbf6abd0c312cb495a3d44e96b745742 << + // + // + // The resulting witness verificaiton script (110 bytes): + // NEO-VM 0 > loadbase64 ABhQDCEC/QqMHOWuVXD91G51mcFrF1vw69/pwXjxq4SPsW2sdKVBxfug4AMAAAAAAQAAAJ4UjUEtUQgwEM6LFMAfDA92ZXJpZnlXaXRoRUNEc2EMFBv1dasRiWiEE2EKNaEohs3gtmxyQWJ9W1I= + // READY: loaded 110 instructions + // NEO-VM 0 > pos + // Error: No help topic for 'pos' + // NEO-VM 0 > ops + // INDEX OPCODE PARAMETER + // 0 PUSHINT8 122 (7a) << + // 2 SWAP + // 3 PUSHDATA1 02fd0a8c1ce5ae5570fdd46e7599c16b175bf0ebdfe9c178f1ab848fb16dac74a5 + // 38 SYSCALL System.Runtime.GetNetwork (c5fba0e0) + // 43 PUSHINT64 4294967296 (0000000001000000) + // 52 ADD + // 53 PUSH4 + // 54 LEFT + // 55 SYSCALL System.Runtime.GetScriptContainer (2d510830) + // 60 PUSH0 + // 61 PICKITEM + // 62 CAT + // 63 PUSH4 + // 64 PACK + // 65 PUSH0 + // 66 PUSHDATA1 766572696679576974684543447361 ("verifyWithECDsa") + // 83 PUSHDATA1 1bf575ab1189688413610a35a12886cde0b66c72 ("NNToUmdQBe5n8o53BTzjTFAnSEcpouyy3B", "0x726cb6e0cd8628a1350a611384688911ab75f51b") + // 105 SYSCALL System.Contract.Call (627d5b52) + } + + // TestVerifyWithECDsa_CustomTxWitness_MultiSig builds custom multisignature witness verification script for Koblitz public keys + // and ensures witness check is passed for the M out of N multisignature of message: + // + // keccak256([4-bytes-network-magic-LE, txHash-bytes-BE]) + // + // The proposed witness verification script has 264 bytes length, verification costs 8390070 * 10e-8GAS including Invocation script execution. + // The users have to sign the keccak256([4-bytes-network-magic-LE, txHash-bytes-BE]). + [TestMethod] + public void TestVerifyWithECDsa_CustomTxWitness_MultiSig() + { + byte[] privkey1 = "b2dde592bfce654ef03f1ceea452d2b0112e90f9f52099bcd86697a2bd0a2b60".HexToBytes(); + ECPoint pubKey1 = ECPoint.Parse("040486468683c112125978ffe876245b2006bfe739aca8539b67335079262cb27ad0dedc9e5583f99b61c6f46bf80b97eaec3654b87add0e5bd7106c69922a229d", ECCurve.Secp256k1); + byte[] privkey2 = "b9879e26941872ee6c9e6f01045681496d8170ed2cc4a54ce617b39ae1891b3a".HexToBytes(); + ECPoint pubKey2 = ECPoint.Parse("040d26fc2ad3b1aae20f040b5f83380670f8ef5c2b2ac921ba3bdd79fd0af0525177715fd4370b1012ddd10579698d186ab342c223da3e884ece9cab9b6638c7bb", ECCurve.Secp256k1); + byte[] privkey3 = "4e1fe2561a6da01ee030589d504d62b23c26bfd56c5e07dfc9b8b74e4602832a".HexToBytes(); + ECPoint pubKey3 = ECPoint.Parse("047b4e72ae854b6a0955b3e02d92651ab7fa641a936066776ad438f95bb674a269a63ff98544691663d91a6cfcd215831f01bfb7a226363a6c5c67ef14541dba07", ECCurve.Secp256k1); + byte[] privkey4 = "6dfd066bb989d3786043aa5c1f0476215d6f5c44f5fc3392dd15e2599b67a728".HexToBytes(); + ECPoint pubKey4 = ECPoint.Parse("04b62ac4c8a352a892feceb18d7e2e3a62c8c1ecbaae5523d89d747b0219276e225be2556a137e0e806e4915762d816cdb43f572730d23bb1b1cba750011c4edc6", ECCurve.Secp256k1); + + // Public keys must be sorted, exactly like for standard CreateMultiSigRedeemScript. + var keys = new List<(byte[], ECPoint)>() + { + (privkey1, pubKey1), + (privkey2, pubKey2), + (privkey3, pubKey3), + (privkey4, pubKey4), + }.OrderBy(k => k.Item2).ToList(); + + // Consider 4 users willing to sign 3/4 multisignature transaction with their Secp256k1 private keys. + var m = 3; + var n = keys.Count(); + + // Must ensure the following conditions are met before verification script construction: + n.Should().BeGreaterThan(0); + m.Should().BeLessThanOrEqualTo(n); + keys.Select(k => k.Item2).Distinct().Count().Should().Be(n); + + // In fact, the following algorithm is implemented via NeoVM instructions: + // + // func Check(sigs []interop.Signature) bool { + // if m != len(sigs) { + // return false + // } + // var pubs []interop.PublicKey = []interop.PublicKey{...} + // msg := append(convert.ToBytes(runtime.GetNetwork()), runtime.GetScriptContainer().Hash...) + // var sigCnt = 0 + // var pubCnt = 0 + // for ; sigCnt < m && pubCnt < n; { // sigs must be sorted by pub + // sigCnt += crypto.VerifyWithECDsa(msg, pubs[pubCnt], sigs[sigCnt], crypto.Secp256k1Keccak256) + // pubCnt++ + // } + // return sigCnt == m + // } + + // vrf is a builder of M out of N multisig witness verification script corresponding to the public keys. + using ScriptBuilder vrf = new(); + + // Start the same way as regular multisig script. + vrf.EmitPush(m); // push m. + foreach (var tuple in keys) + { + vrf.EmitPush(tuple.Item2.EncodePoint(true)); // push public keys in compressed form. + } + vrf.EmitPush(n); // push n. + + // Initialize slots for local variables. Locals slot scheme: + // LOC0 -> sigs + // LOC1 -> pubs + // LOC2 -> msg (ByteString) + // LOC3 -> sigCnt (Integer) + // LOC4 -> pubCnt (Integer) + // LOC5 -> n + // LOC6 -> m + vrf.Emit(OpCode.INITSLOT, new ReadOnlySpan([7, 0])); // 7 locals, no args. + + // Store n. + vrf.Emit(OpCode.STLOC5); + + // Pack public keys and store at LOC1. + vrf.Emit(OpCode.LDLOC5, // load n. + OpCode.PACK, OpCode.STLOC1); // pack pubs and store. + + // Store m. + vrf.Emit(OpCode.STLOC6); + + // Check the number of signatures is m. Abort the execution if not. + vrf.Emit(OpCode.DEPTH); // push the number of signatures onto stack. + vrf.Emit(OpCode.LDLOC6); // load m. + vrf.Emit(OpCode.JMPEQ, new ReadOnlySpan([0])); // here and below short jumps are sufficient. Offset will be filled later. + var sigsLenCheckEndOffset = vrf.Length; + vrf.Emit(OpCode.ABORT); // abort the execution if length of the signatures not equal to m. + + // Start the verification itself. + var checkStartOffset = vrf.Length; + + // Pack signatures and store at LOC0. + vrf.Emit(OpCode.LDLOC6); // load m. + vrf.Emit(OpCode.PACK, OpCode.STLOC0); + + // Get message and store it at LOC2. + // msg = [4-network-magic-bytes-LE, tx-hash-BE] + vrf.EmitSysCall(ApplicationEngine.System_Runtime_GetNetwork); // push network magic (Integer stackitem), can have 0-5 bytes length serialized. + // Convert network magic to 4-bytes-length LE byte array representation. + vrf.EmitPush(0x100000000); // push 0x100000000. + vrf.Emit(OpCode.ADD, // the result is some new number that is 5 bytes at least when serialized, but first 4 bytes are intact network value (LE). + OpCode.PUSH4, OpCode.LEFT); // cut the first 4 bytes out of a number that is at least 5 bytes long, the result is 4-bytes-length LE network representation. + // Retrieve executing transaction hash. + vrf.EmitSysCall(ApplicationEngine.System_Runtime_GetScriptContainer); // push the script container (executing transaction, actually). + vrf.Emit(OpCode.PUSH0, OpCode.PICKITEM); // pick 0-th transaction item (the transaction hash). + // Concatenate network magic and transaction hash. + vrf.Emit(OpCode.CAT); // this instruction will convert network magic to bytes using BigInteger rules of conversion. + vrf.Emit(OpCode.STLOC2); // store msg as a local variable #2. + + // Initialize local variables: sigCnt, pubCnt. + vrf.Emit(OpCode.PUSH0, OpCode.STLOC3, // initialize sigCnt. + OpCode.PUSH0, OpCode.STLOC4); // initialize pubCnt. + + // Loop condition check. + var loopStartOffset = vrf.Length; + vrf.Emit(OpCode.LDLOC3); // load sigCnt. + vrf.Emit(OpCode.LDLOC6); // load m. + vrf.Emit(OpCode.GE, // sigCnt >= m + OpCode.LDLOC4); // load pubCnt + vrf.Emit(OpCode.LDLOC5); // load n. + vrf.Emit(OpCode.GE, // pubCnt >= n + OpCode.OR); // sigCnt >= m || pubCnt >= n + vrf.Emit(OpCode.JMPIF, new ReadOnlySpan([0])); // jump to the end of the script if (sigCnt >= m || pubCnt >= n). + var loopConditionOffset = vrf.Length; + + // Loop start. Prepare arguments and call CryptoLib's verifyWithECDsa. + vrf.EmitPush((byte)NamedCurveHash.secp256k1Keccak256); // push Koblitz curve identifier and Keccak256 hasher. + vrf.Emit(OpCode.LDLOC0, // load signatures. + OpCode.LDLOC3, // load sigCnt. + OpCode.PICKITEM, // pick signature at index sigCnt. + OpCode.LDLOC1, // load pubs. + OpCode.LDLOC4, // load pubCnt. + OpCode.PICKITEM, // pick pub at index pubCnt. + OpCode.LDLOC2, // load msg. + OpCode.PUSH4, OpCode.PACK); // pack 4 arguments for 'verifyWithECDsa' call. + EmitAppCallNoArgs(vrf, CryptoLib.CryptoLib.Hash, "verifyWithECDsa", CallFlags.None); // emit the call to 'verifyWithECDsa' itself. + + // Update loop variables. + vrf.Emit(OpCode.LDLOC3, OpCode.ADD, OpCode.STLOC3, // increment sigCnt if signature is valid. + OpCode.LDLOC4, OpCode.INC, OpCode.STLOC4); // increment pubCnt. + + // End of the loop. + vrf.Emit(OpCode.JMP, new ReadOnlySpan([0])); // jump to the start of cycle. + var loopEndOffset = vrf.Length; + // Return condition: the number of valid signatures should be equal to m. + var progRetOffset = vrf.Length; + vrf.Emit(OpCode.LDLOC3); // load sigCnt. + vrf.Emit(OpCode.LDLOC6); // load m. + vrf.Emit(OpCode.NUMEQUAL); // push m == sigCnt. + + var vrfScript = vrf.ToArray(); + + // Set JMP* instructions offsets. "-1" is for short JMP parameter offset. JMP parameters + // are relative offsets. + vrfScript[sigsLenCheckEndOffset - 1] = (byte)(checkStartOffset - sigsLenCheckEndOffset + 2); + vrfScript[loopEndOffset - 1] = (byte)(loopStartOffset - loopEndOffset + 2); + vrfScript[loopConditionOffset - 1] = (byte)(progRetOffset - loopConditionOffset + 2); + + // Account is a hash of verification script. + var acc = vrfScript.ToScriptHash(); + + var tx = new Transaction + { + Attributes = [], + NetworkFee = 1_0000_0000, + Nonce = (uint)Environment.TickCount, + Script = new byte[Transaction.MaxTransactionSize / 100], + Signers = [new Signer { Account = acc }], + SystemFee = 0, + ValidUntilBlock = 10, + Version = 0, + Witnesses = [] + }; + // inv is a builder of witness invocation script corresponding to the public key. + using ScriptBuilder inv = new(); + for (var i = 0; i < n; i++) + { + if (i == 1) // Skip one key since we need only 3 signatures. + continue; + var sig = Crypto.Sign(tx.GetSignData(TestBlockchain.TheNeoSystem.Settings.Network), keys[i].Item1, ECCurve.Secp256k1, Hasher.Keccak256); + inv.EmitPush(sig); + } + + tx.Witnesses = + [ + new Witness { InvocationScript = inv.ToArray(), VerificationScript = vrfScript } + ]; + + tx.VerifyStateIndependent(TestProtocolSettings.Default).Should().Be(VerifyResult.Succeed); + + var snapshot = TestBlockchain.GetTestSnapshot(); + + // Create fake balance to pay the fees. + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: long.MaxValue); + _ = NativeContract.GAS.Mint(engine, acc, 5_0000_0000, false); + snapshot.Commit(); + + // Check that witness verification passes. + var txVrfContext = new TransactionVerificationContext(); + var conflicts = new List(); + tx.VerifyStateDependent(TestProtocolSettings.Default, snapshot, txVrfContext, conflicts).Should().Be(VerifyResult.Succeed); + + // The resulting witness verification cost for 3/4 multisig is 8389470 * 10e-8GAS. Cost depends on M/N. + // The resulting witness Invocation script (198 bytes for 3 signatures): + // NEO-VM 0 > loadbase64 DEDM23XByPvDK9XRAHRhfGH7/Mp5jdaci3/GpTZ3D9SZx2Zw89tAaOtmQSIutXbCxRQA1kSeUD4AteJGoNXFhFzIDECgeHoey0rYdlFyTVfDJSsuS+VwzC5OtYGCVR2V/MttmLXWA/FWZH/MjmU0obgQXa9zoBxqYQUUJKefivZFxVcTDEAZT6L6ZFybeXbm8+RlVNS7KshusT54d2ImQ6vFvxETphhJOwcQ0yNL6qJKsrLAKAnzicY4az3ct0G35mI17/gQ + // READY: loaded 198 instructions + // NEO-VM 0 > ops + // INDEX OPCODE PARAMETER + // 0 PUSHDATA1 ccdb75c1c8fbc32bd5d10074617c61fbfcca798dd69c8b7fc6a536770fd499c76670f3db4068eb6641222eb576c2c51400d6449e503e00b5e246a0d5c5845cc8 << + // 66 PUSHDATA1 a0787a1ecb4ad87651724d57c3252b2e4be570cc2e4eb58182551d95fccb6d98b5d603f156647fcc8e6534a1b8105daf73a01c6a61051424a79f8af645c55713 + // 132 PUSHDATA1 194fa2fa645c9b7976e6f3e46554d4bb2ac86eb13e7877622643abc5bf1113a618493b0710d3234beaa24ab2b2c02809f389c6386b3ddcb741b7e66235eff810 + // + // + // Resulting witness verification script (266 bytes for 3/4 multisig): + // NEO-VM 0 > loadbase64 EwwhAwSGRoaDwRISWXj/6HYkWyAGv+c5rKhTm2czUHkmLLJ6DCEDDSb8KtOxquIPBAtfgzgGcPjvXCsqySG6O915/QrwUlEMIQN7TnKuhUtqCVWz4C2SZRq3+mQak2Bmd2rUOPlbtnSiaQwhArYqxMijUqiS/s6xjX4uOmLIwey6rlUj2J10ewIZJ24iFFcHAHVtwHF2Q24oAzhuwHBBxfug4AMAAAAAAQAAAJ4UjUEtUQgwEM6LchBzEHRrbrhsbbiSJEIAGGhrzmlszmoUwB8MD3ZlcmlmeVdpdGhFQ0RzYQwUG/V1qxGJaIQTYQo1oSiGzeC2bHJBYn1bUmuec2ycdCK5a26z + // READY: loaded 264 instructions + // NEO-VM 0 > ops + // INDEX OPCODE PARAMETER + // 0 PUSH3 << + // 1 PUSHDATA1 030486468683c112125978ffe876245b2006bfe739aca8539b67335079262cb27a + // 36 PUSHDATA1 030d26fc2ad3b1aae20f040b5f83380670f8ef5c2b2ac921ba3bdd79fd0af05251 + // 71 PUSHDATA1 037b4e72ae854b6a0955b3e02d92651ab7fa641a936066776ad438f95bb674a269 + // 106 PUSHDATA1 02b62ac4c8a352a892feceb18d7e2e3a62c8c1ecbaae5523d89d747b0219276e22 + // 141 PUSH4 + // 142 INITSLOT 7 local, 0 arg + // 145 STLOC5 + // 146 LDLOC5 + // 147 PACK + // 148 STLOC1 + // 149 STLOC6 + // 150 DEPTH + // 151 LDLOC6 + // 152 JMPEQ 155 (3/03) + // 154 ABORT + // 155 LDLOC6 + // 156 PACK + // 157 STLOC0 + // 158 SYSCALL System.Runtime.GetNetwork (c5fba0e0) + // 163 PUSHINT64 4294967296 (0000000001000000) + // 172 ADD + // 173 PUSH4 + // 174 LEFT + // 175 SYSCALL System.Runtime.GetScriptContainer (2d510830) + // 180 PUSH0 + // 181 PICKITEM + // 182 CAT + // 183 STLOC2 + // 184 PUSH0 + // 185 STLOC3 + // 186 PUSH0 + // 187 STLOC4 + // 188 LDLOC3 + // 189 LDLOC6 + // 190 GE + // 191 LDLOC4 + // 192 LDLOC5 + // 193 GE + // 194 OR + // 195 JMPIF 261 (66/42) + // 197 PUSHINT8 122 (7a) + // 199 LDLOC0 + // 200 LDLOC3 + // 201 PICKITEM + // 202 LDLOC1 + // 203 LDLOC4 + // 204 PICKITEM + // 205 LDLOC2 + // 206 PUSH4 + // 207 PACK + // 208 PUSH0 + // 209 PUSHDATA1 766572696679576974684543447361 ("verifyWithECDsa") + // 226 PUSHDATA1 1bf575ab1189688413610a35a12886cde0b66c72 ("NNToUmdQBe5n8o53BTzjTFAnSEcpouyy3B", "0x726cb6e0cd8628a1350a611384688911ab75f51b") + // 248 SYSCALL System.Contract.Call (627d5b52) + // 253 LDLOC3 + // 254 ADD + // 255 STLOC3 + // 256 LDLOC4 + // 257 INC + // 258 STLOC4 + // 259 JMP 188 (-71/b9) + // 261 LDLOC3 + // 262 LDLOC6 + // 263 NUMEQUAL + } + + // EmitAppCallNoArgs is a helper method that emits all parameters of System.Contract.Call interop + // except the method arguments. + private static ScriptBuilder EmitAppCallNoArgs(ScriptBuilder builder, UInt160 contractHash, string method, CallFlags f) + { + builder.EmitPush((byte)f); + builder.EmitPush(method); + builder.EmitPush(contractHash); + builder.EmitSysCall(ApplicationEngine.System_Contract_Call); + return builder; + } + + [TestMethod] + public void TestVerifyWithECDsa() + { + byte[] privR1 = "6e63fda41e9e3aba9bb5696d58a75731f044a9bdc48fe546da571543b2fa460e".HexToBytes(); + ECPoint pubR1 = ECPoint.Parse("04cae768e1cf58d50260cab808da8d6d83d5d3ab91eac41cdce577ce5862d736413643bdecd6d21c3b66f122ab080f9219204b10aa8bbceb86c1896974768648f3", ECCurve.Secp256r1); + byte[] privK1 = "0b5fb3a050385196b327be7d86cbce6e40a04c8832445af83ad19c82103b3ed9".HexToBytes(); + ECPoint pubK1 = ECPoint.Parse("04b6363b353c3ee1620c5af58594458aa00abf43a6d134d7c4cb2d901dc0f474fd74c94740bd7169aa0b1ef7bc657e824b1d7f4283c547e7ec18c8576acf84418a", ECCurve.Secp256k1); + byte[] message = System.Text.Encoding.Default.GetBytes("HelloWorld"); + + // secp256r1 + SHA256 + byte[] signature = Crypto.Sign(message, privR1, ECCurve.Secp256r1, Hasher.SHA256); + Crypto.VerifySignature(message, signature, pubR1).Should().BeTrue(); // SHA256 hash is used by default. + CallVerifyWithECDsa(message, pubR1, signature, NamedCurveHash.secp256r1SHA256).Should().Be(true); + + // secp256r1 + Keccak256 + signature = Crypto.Sign(message, privR1, ECCurve.Secp256r1, Hasher.Keccak256); + Crypto.VerifySignature(message, signature, pubR1, Hasher.Keccak256).Should().BeTrue(); + CallVerifyWithECDsa(message, pubR1, signature, NamedCurveHash.secp256r1Keccak256).Should().Be(true); + + // secp256k1 + SHA256 + signature = Crypto.Sign(message, privK1, ECCurve.Secp256k1, Hasher.SHA256); + Crypto.VerifySignature(message, signature, pubK1).Should().BeTrue(); // SHA256 hash is used by default. + CallVerifyWithECDsa(message, pubK1, signature, NamedCurveHash.secp256k1SHA256).Should().Be(true); + + // secp256k1 + Keccak256 + signature = Crypto.Sign(message, privK1, ECCurve.Secp256k1, Hasher.Keccak256); + Crypto.VerifySignature(message, signature, pubK1, Hasher.Keccak256).Should().BeTrue(); + CallVerifyWithECDsa(message, pubK1, signature, NamedCurveHash.secp256k1Keccak256).Should().Be(true); + } + + private bool CallVerifyWithECDsa(byte[] message, ECPoint pub, byte[] signature, NamedCurveHash curveHash) + { + var snapshot = TestBlockchain.GetTestSnapshot(); + using (ScriptBuilder script = new()) + { + script.EmitPush((int)curveHash); + script.EmitPush(signature); + script.EmitPush(pub.EncodePoint(true)); + script.EmitPush(message); + script.EmitPush(4); + script.Emit(OpCode.PACK); + script.EmitPush(CallFlags.All); + script.EmitPush("verifyWithECDsa"); + script.EmitPush(NativeContract.CryptoLib.Hash); + script.EmitSysCall(ApplicationEngine.System_Contract_Call); + + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + engine.LoadScript(script.ToArray()); + Assert.AreEqual(VMState.HALT, engine.Execute()); + return engine.ResultStack.Pop().GetBoolean(); + } + } } } diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_FungibleToken.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_FungibleToken.cs index 966f0b460c..5d6d00986e 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_FungibleToken.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_FungibleToken.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_FungibleToken.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Akka.TestKit.Xunit2; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_GasToken.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_GasToken.cs index 997c4ce335..51617b6744 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_GasToken.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_GasToken.cs @@ -1,7 +1,14 @@ -using System; -using System.Linq; -using System.Numerics; -using System.Threading.Tasks; +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_GasToken.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; @@ -10,6 +17,10 @@ using Neo.SmartContract; using Neo.SmartContract.Native; using Neo.UnitTests.Extensions; +using System; +using System.Linq; +using System.Numerics; +using System.Threading.Tasks; namespace Neo.UnitTests.SmartContract.Native { @@ -40,7 +51,7 @@ public async Task Check_BalanceOfTransferAndBurn() { var snapshot = _snapshot.CreateSnapshot(); var persistingBlock = new Block { Header = new Header { Index = 1000 } }; - byte[] from = Contract.GetBFTAddress(ProtocolSettings.Default.StandbyValidators).ToArray(); + byte[] from = Contract.GetBFTAddress(TestProtocolSettings.Default.StandbyValidators).ToArray(); byte[] to = new byte[20]; var supply = NativeContract.GAS.TotalSupply(snapshot); supply.Should().Be(5200000050000000); // 3000000000000000 + 50000000 (neo holder reward) diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs index 710502bef5..1989b4323b 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs @@ -1,15 +1,161 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_NativeContract.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.SmartContract; using Neo.SmartContract.Native; +using Neo.VM; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Numerics; namespace Neo.UnitTests.SmartContract.Native { [TestClass] public class UT_NativeContract { + private DataCache _snapshot; + /// + /// _nativeStates contains a mapping from native contract name to expected native contract state + /// constructed with all hardforks enabled and marshalled in JSON. + /// + private Dictionary _nativeStates; + + [TestInitialize] + public void TestSetup() + { + _snapshot = TestBlockchain.GetTestSnapshot(); + _nativeStates = new Dictionary + { + {"ContractManagement", """{"id":-1,"updatecounter":0,"hash":"0xfffdc93764dbaddd97c48f252a53ea4643faa3fd","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1094259016},"manifest":{"name":"ContractManagement","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"deploy","parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"}],"returntype":"Array","offset":0,"safe":false},{"name":"deploy","parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Array","offset":7,"safe":false},{"name":"destroy","parameters":[],"returntype":"Void","offset":14,"safe":false},{"name":"getContract","parameters":[{"name":"hash","type":"Hash160"}],"returntype":"Array","offset":21,"safe":true},{"name":"getContractById","parameters":[{"name":"id","type":"Integer"}],"returntype":"Array","offset":28,"safe":true},{"name":"getContractHashes","parameters":[],"returntype":"InteropInterface","offset":35,"safe":true},{"name":"getMinimumDeploymentFee","parameters":[],"returntype":"Integer","offset":42,"safe":true},{"name":"hasMethod","parameters":[{"name":"hash","type":"Hash160"},{"name":"method","type":"String"},{"name":"pcount","type":"Integer"}],"returntype":"Boolean","offset":49,"safe":true},{"name":"setMinimumDeploymentFee","parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","offset":56,"safe":false},{"name":"update","parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"}],"returntype":"Void","offset":63,"safe":false},{"name":"update","parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","offset":70,"safe":false}],"events":[{"name":"Deploy","parameters":[{"name":"Hash","type":"Hash160"}]},{"name":"Update","parameters":[{"name":"Hash","type":"Hash160"}]},{"name":"Destroy","parameters":[{"name":"Hash","type":"Hash160"}]}]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}""" }, + {"StdLib", """{"id":-2,"updatecounter":0,"hash":"0xacce6fd80d44e1796aa0c2c625e9e4e0ce39efc0","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dA","checksum":1991619121},"manifest":{"name":"StdLib","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"atoi","parameters":[{"name":"value","type":"String"}],"returntype":"Integer","offset":0,"safe":true},{"name":"atoi","parameters":[{"name":"value","type":"String"},{"name":"base","type":"Integer"}],"returntype":"Integer","offset":7,"safe":true},{"name":"base58CheckDecode","parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","offset":14,"safe":true},{"name":"base58CheckEncode","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","offset":21,"safe":true},{"name":"base58Decode","parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","offset":28,"safe":true},{"name":"base58Encode","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","offset":35,"safe":true},{"name":"base64Decode","parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","offset":42,"safe":true},{"name":"base64Encode","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","offset":49,"safe":true},{"name":"deserialize","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"Any","offset":56,"safe":true},{"name":"itoa","parameters":[{"name":"value","type":"Integer"}],"returntype":"String","offset":63,"safe":true},{"name":"itoa","parameters":[{"name":"value","type":"Integer"},{"name":"base","type":"Integer"}],"returntype":"String","offset":70,"safe":true},{"name":"jsonDeserialize","parameters":[{"name":"json","type":"ByteArray"}],"returntype":"Any","offset":77,"safe":true},{"name":"jsonSerialize","parameters":[{"name":"item","type":"Any"}],"returntype":"ByteArray","offset":84,"safe":true},{"name":"memoryCompare","parameters":[{"name":"str1","type":"ByteArray"},{"name":"str2","type":"ByteArray"}],"returntype":"Integer","offset":91,"safe":true},{"name":"memorySearch","parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"}],"returntype":"Integer","offset":98,"safe":true},{"name":"memorySearch","parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"},{"name":"start","type":"Integer"}],"returntype":"Integer","offset":105,"safe":true},{"name":"memorySearch","parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"},{"name":"start","type":"Integer"},{"name":"backward","type":"Boolean"}],"returntype":"Integer","offset":112,"safe":true},{"name":"serialize","parameters":[{"name":"item","type":"Any"}],"returntype":"ByteArray","offset":119,"safe":true},{"name":"strLen","parameters":[{"name":"str","type":"String"}],"returntype":"Integer","offset":126,"safe":true},{"name":"stringSplit","parameters":[{"name":"str","type":"String"},{"name":"separator","type":"String"}],"returntype":"Array","offset":133,"safe":true},{"name":"stringSplit","parameters":[{"name":"str","type":"String"},{"name":"separator","type":"String"},{"name":"removeEmptyEntries","type":"Boolean"}],"returntype":"Array","offset":140,"safe":true}],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}"""}, + {"CryptoLib", """{"id":-3,"updatecounter":0,"hash":"0x726cb6e0cd8628a1350a611384688911ab75f51b","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1094259016},"manifest":{"name":"CryptoLib","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"bls12381Add","parameters":[{"name":"x","type":"InteropInterface"},{"name":"y","type":"InteropInterface"}],"returntype":"InteropInterface","offset":0,"safe":true},{"name":"bls12381Deserialize","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"InteropInterface","offset":7,"safe":true},{"name":"bls12381Equal","parameters":[{"name":"x","type":"InteropInterface"},{"name":"y","type":"InteropInterface"}],"returntype":"Boolean","offset":14,"safe":true},{"name":"bls12381Mul","parameters":[{"name":"x","type":"InteropInterface"},{"name":"mul","type":"ByteArray"},{"name":"neg","type":"Boolean"}],"returntype":"InteropInterface","offset":21,"safe":true},{"name":"bls12381Pairing","parameters":[{"name":"g1","type":"InteropInterface"},{"name":"g2","type":"InteropInterface"}],"returntype":"InteropInterface","offset":28,"safe":true},{"name":"bls12381Serialize","parameters":[{"name":"g","type":"InteropInterface"}],"returntype":"ByteArray","offset":35,"safe":true},{"name":"keccak256","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","offset":42,"safe":true},{"name":"murmur32","parameters":[{"name":"data","type":"ByteArray"},{"name":"seed","type":"Integer"}],"returntype":"ByteArray","offset":49,"safe":true},{"name":"ripemd160","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","offset":56,"safe":true},{"name":"sha256","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","offset":63,"safe":true},{"name":"verifyWithECDsa","parameters":[{"name":"message","type":"ByteArray"},{"name":"pubkey","type":"ByteArray"},{"name":"signature","type":"ByteArray"},{"name":"curveHash","type":"Integer"}],"returntype":"Boolean","offset":70,"safe":true}],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}"""}, + {"LedgerContract", """{"id":-4,"updatecounter":0,"hash":"0xda65b600f7124ce6c79950c1772a36403104f2be","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1110259869},"manifest":{"name":"LedgerContract","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"currentHash","parameters":[],"returntype":"Hash256","offset":0,"safe":true},{"name":"currentIndex","parameters":[],"returntype":"Integer","offset":7,"safe":true},{"name":"getBlock","parameters":[{"name":"indexOrHash","type":"ByteArray"}],"returntype":"Array","offset":14,"safe":true},{"name":"getTransaction","parameters":[{"name":"hash","type":"Hash256"}],"returntype":"Array","offset":21,"safe":true},{"name":"getTransactionFromBlock","parameters":[{"name":"blockIndexOrHash","type":"ByteArray"},{"name":"txIndex","type":"Integer"}],"returntype":"Array","offset":28,"safe":true},{"name":"getTransactionHeight","parameters":[{"name":"hash","type":"Hash256"}],"returntype":"Integer","offset":35,"safe":true},{"name":"getTransactionSigners","parameters":[{"name":"hash","type":"Hash256"}],"returntype":"Array","offset":42,"safe":true},{"name":"getTransactionVMState","parameters":[{"name":"hash","type":"Hash256"}],"returntype":"Integer","offset":49,"safe":true}],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}"""}, + {"NeoToken", """{"id":-5,"updatecounter":0,"hash":"0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1325686241},"manifest":{"name":"NeoToken","groups":[],"features":{},"supportedstandards":["NEP-17"],"abi":{"methods":[{"name":"balanceOf","parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer","offset":0,"safe":true},{"name":"decimals","parameters":[],"returntype":"Integer","offset":7,"safe":true},{"name":"getAccountState","parameters":[{"name":"account","type":"Hash160"}],"returntype":"Array","offset":14,"safe":true},{"name":"getAllCandidates","parameters":[],"returntype":"InteropInterface","offset":21,"safe":true},{"name":"getCandidateVote","parameters":[{"name":"pubKey","type":"PublicKey"}],"returntype":"Integer","offset":28,"safe":true},{"name":"getCandidates","parameters":[],"returntype":"Array","offset":35,"safe":true},{"name":"getCommittee","parameters":[],"returntype":"Array","offset":42,"safe":true},{"name":"getCommitteeAddress","parameters":[],"returntype":"Hash160","offset":49,"safe":true},{"name":"getGasPerBlock","parameters":[],"returntype":"Integer","offset":56,"safe":true},{"name":"getNextBlockValidators","parameters":[],"returntype":"Array","offset":63,"safe":true},{"name":"getRegisterPrice","parameters":[],"returntype":"Integer","offset":70,"safe":true},{"name":"registerCandidate","parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean","offset":77,"safe":false},{"name":"setGasPerBlock","parameters":[{"name":"gasPerBlock","type":"Integer"}],"returntype":"Void","offset":84,"safe":false},{"name":"setRegisterPrice","parameters":[{"name":"registerPrice","type":"Integer"}],"returntype":"Void","offset":91,"safe":false},{"name":"symbol","parameters":[],"returntype":"String","offset":98,"safe":true},{"name":"totalSupply","parameters":[],"returntype":"Integer","offset":105,"safe":true},{"name":"transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Boolean","offset":112,"safe":false},{"name":"unclaimedGas","parameters":[{"name":"account","type":"Hash160"},{"name":"end","type":"Integer"}],"returntype":"Integer","offset":119,"safe":true},{"name":"unregisterCandidate","parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean","offset":126,"safe":false},{"name":"vote","parameters":[{"name":"account","type":"Hash160"},{"name":"voteTo","type":"PublicKey"}],"returntype":"Boolean","offset":133,"safe":false}],"events":[{"name":"Transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}]},{"name":"CandidateStateChanged","parameters":[{"name":"pubkey","type":"PublicKey"},{"name":"registered","type":"Boolean"},{"name":"votes","type":"Integer"}]},{"name":"Vote","parameters":[{"name":"account","type":"Hash160"},{"name":"from","type":"PublicKey"},{"name":"to","type":"PublicKey"},{"name":"amount","type":"Integer"}]},{"name":"CommitteeChanged","parameters":[{"name":"old","type":"Array"},{"name":"new","type":"Array"}]}]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}"""}, + {"GasToken", """{"id":-6,"updatecounter":0,"hash":"0xd2a4cff31913016155e38e474a2c06d08be276cf","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":2663858513},"manifest":{"name":"GasToken","groups":[],"features":{},"supportedstandards":["NEP-17"],"abi":{"methods":[{"name":"balanceOf","parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer","offset":0,"safe":true},{"name":"decimals","parameters":[],"returntype":"Integer","offset":7,"safe":true},{"name":"symbol","parameters":[],"returntype":"String","offset":14,"safe":true},{"name":"totalSupply","parameters":[],"returntype":"Integer","offset":21,"safe":true},{"name":"transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Boolean","offset":28,"safe":false}],"events":[{"name":"Transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}]}]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}"""}, + {"PolicyContract", """{"id":-7,"updatecounter":0,"hash":"0xcc5e4edd9f5f8dba8bb65734541df7a1c081c67b","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1094259016},"manifest":{"name":"PolicyContract","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"blockAccount","parameters":[{"name":"account","type":"Hash160"}],"returntype":"Boolean","offset":0,"safe":false},{"name":"getAttributeFee","parameters":[{"name":"attributeType","type":"Integer"}],"returntype":"Integer","offset":7,"safe":true},{"name":"getExecFeeFactor","parameters":[],"returntype":"Integer","offset":14,"safe":true},{"name":"getFeePerByte","parameters":[],"returntype":"Integer","offset":21,"safe":true},{"name":"getStoragePrice","parameters":[],"returntype":"Integer","offset":28,"safe":true},{"name":"isBlocked","parameters":[{"name":"account","type":"Hash160"}],"returntype":"Boolean","offset":35,"safe":true},{"name":"setAttributeFee","parameters":[{"name":"attributeType","type":"Integer"},{"name":"value","type":"Integer"}],"returntype":"Void","offset":42,"safe":false},{"name":"setExecFeeFactor","parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","offset":49,"safe":false},{"name":"setFeePerByte","parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","offset":56,"safe":false},{"name":"setStoragePrice","parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","offset":63,"safe":false},{"name":"unblockAccount","parameters":[{"name":"account","type":"Hash160"}],"returntype":"Boolean","offset":70,"safe":false}],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}"""}, + {"RoleManagement", """{"id":-8,"updatecounter":0,"hash":"0x49cf4e5378ffcd4dec034fd98a174c5491e395e2","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0A=","checksum":983638438},"manifest":{"name":"RoleManagement","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"designateAsRole","parameters":[{"name":"role","type":"Integer"},{"name":"nodes","type":"Array"}],"returntype":"Void","offset":0,"safe":false},{"name":"getDesignatedByRole","parameters":[{"name":"role","type":"Integer"},{"name":"index","type":"Integer"}],"returntype":"Array","offset":7,"safe":true}],"events":[{"name":"Designation","parameters":[{"name":"Role","type":"Integer"},{"name":"BlockIndex","type":"Integer"}]}]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}"""}, + {"OracleContract", """{"id":-9,"updatecounter":0,"hash":"0xfe924b7cfe89ddd271abaf7210a80a7e11178758","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":2663858513},"manifest":{"name":"OracleContract","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"finish","parameters":[],"returntype":"Void","offset":0,"safe":false},{"name":"getPrice","parameters":[],"returntype":"Integer","offset":7,"safe":true},{"name":"request","parameters":[{"name":"url","type":"String"},{"name":"filter","type":"String"},{"name":"callback","type":"String"},{"name":"userData","type":"Any"},{"name":"gasForResponse","type":"Integer"}],"returntype":"Void","offset":14,"safe":false},{"name":"setPrice","parameters":[{"name":"price","type":"Integer"}],"returntype":"Void","offset":21,"safe":false},{"name":"verify","parameters":[],"returntype":"Boolean","offset":28,"safe":true}],"events":[{"name":"OracleRequest","parameters":[{"name":"Id","type":"Integer"},{"name":"RequestContract","type":"Hash160"},{"name":"Url","type":"String"},{"name":"Filter","type":"String"}]},{"name":"OracleResponse","parameters":[{"name":"Id","type":"Integer"},{"name":"OriginalTx","type":"Hash256"}]}]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}"""}, + }; + } + + [TestCleanup] + public void Clean() + { + TestBlockchain.ResetStore(); + } + [TestMethod] public void TestGetContract() { Assert.IsTrue(NativeContract.NEO == NativeContract.GetContract(NativeContract.NEO.Hash)); } + + [TestMethod] + public void TestIsInitializeBlock() + { + string json = UT_ProtocolSettings.CreateHKSettings("\"HF_Cockatrice\": 20"); + + var file = Path.GetTempFileName(); + File.WriteAllText(file, json); + ProtocolSettings settings = ProtocolSettings.Load(file, false); + File.Delete(file); + + Assert.IsTrue(NativeContract.CryptoLib.IsInitializeBlock(settings, 0, out var hf)); + Assert.IsNotNull(hf); + Assert.AreEqual(0, hf.Length); + + Assert.IsFalse(NativeContract.CryptoLib.IsInitializeBlock(settings, 1, out hf)); + Assert.IsNull(hf); + + Assert.IsTrue(NativeContract.CryptoLib.IsInitializeBlock(settings, 20, out hf)); + Assert.AreEqual(1, hf.Length); + Assert.AreEqual(Hardfork.HF_Cockatrice, hf[0]); + } + + [TestMethod] + public void TestGenesisNEP17Manifest() + { + var persistingBlock = new Block + { + Header = new Header + { + Index = 1, + MerkleRoot = UInt256.Zero, + NextConsensus = UInt160.Zero, + PrevHash = UInt256.Zero, + Witness = new Witness() { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() } + }, + Transactions = [] + }; + var snapshot = _snapshot.CreateSnapshot(); + + // Ensure that native NEP17 contracts contain proper supported standards and events declared + // in the manifest constructed for all hardforks enabled. Ref. https://github.com/neo-project/neo/pull/3195. + foreach (var h in new List() { NativeContract.GAS.Hash, NativeContract.NEO.Hash }) + { + var state = Call_GetContract(snapshot, h, persistingBlock); + Assert.IsTrue(state.Manifest.SupportedStandards.Contains("NEP-17")); + Assert.AreEqual(1, state.Manifest.Abi.Events.Where(e => e.Name == "Transfer").Count()); + } + } + + [TestMethod] + public void TestGenesisNativeState() + { + var persistingBlock = new Block + { + Header = new Header + { + Index = 1, + MerkleRoot = UInt256.Zero, + NextConsensus = UInt160.Zero, + PrevHash = UInt256.Zero, + Witness = new Witness() { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() } + }, + Transactions = [] + }; + var snapshot = _snapshot.CreateSnapshot(); + + // Ensure that all native contracts have proper state generated with an assumption that + // all hardforks enabled. + foreach (var ctr in NativeContract.Contracts) + { + var state = Call_GetContract(snapshot, ctr.Hash, persistingBlock); + Assert.AreEqual(_nativeStates[ctr.Name], state.ToJson().ToString()); + } + } + + internal static ContractState Call_GetContract(DataCache snapshot, UInt160 address, Block persistingBlock) + { + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); + + using var script = new ScriptBuilder(); + script.EmitDynamicCall(NativeContract.ContractManagement.Hash, "getContract", address); + engine.LoadScript(script.ToArray()); + + engine.Execute().Should().Be(VMState.HALT); + + var result = engine.ResultStack.Pop(); + result.Should().BeOfType(typeof(VM.Types.Array)); + + var cs = new ContractState(); + ((IInteroperable)cs).FromStackItem(result); + + return cs; + } } } diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_NeoToken.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_NeoToken.cs index ee1ee4753e..0a19314153 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_NeoToken.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_NeoToken.cs @@ -1,6 +1,14 @@ -using System; -using System.Linq; -using System.Numerics; +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_NeoToken.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; @@ -12,6 +20,9 @@ using Neo.UnitTests.Extensions; using Neo.VM; using Neo.Wallets; +using System; +using System.Linq; +using System.Numerics; using static Neo.SmartContract.Native.NeoToken; namespace Neo.UnitTests.SmartContract.Native @@ -51,7 +62,7 @@ public void Check_Vote() var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); snapshot.Add(storageKey, new StorageItem(new HashIndexState { Hash = UInt256.Zero, Index = persistingBlock.Index - 1 })); - byte[] from = Contract.GetBFTAddress(ProtocolSettings.Default.StandbyValidators).ToArray(); + byte[] from = Contract.GetBFTAddress(TestProtocolSettings.Default.StandbyValidators).ToArray(); // No signature @@ -109,7 +120,7 @@ public void Check_Vote_Sameaccounts() var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); snapshot.Add(storageKey, new StorageItem(new HashIndexState { Hash = UInt256.Zero, Index = persistingBlock.Index - 1 })); - byte[] from = Contract.GetBFTAddress(ProtocolSettings.Default.StandbyValidators).ToArray(); + byte[] from = Contract.GetBFTAddress(TestProtocolSettings.Default.StandbyValidators).ToArray(); var accountState = snapshot.TryGet(CreateStorageKey(20, from)).GetInteroperable(); accountState.Balance = 100; snapshot.Add(CreateStorageKey(33, ECCurve.Secp256r1.G.ToArray()), new StorageItem(new CandidateState() { Registered = true })); @@ -141,8 +152,8 @@ public void Check_Vote_ChangeVote() var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); snapshot.Add(storageKey, new StorageItem(new HashIndexState { Hash = UInt256.Zero, Index = persistingBlock.Index - 1 })); //from vote to G - byte[] from = ProtocolSettings.Default.StandbyValidators[0].ToArray(); - var from_Account = Contract.CreateSignatureContract(ProtocolSettings.Default.StandbyValidators[0]).ScriptHash.ToArray(); + byte[] from = TestProtocolSettings.Default.StandbyValidators[0].ToArray(); + var from_Account = Contract.CreateSignatureContract(TestProtocolSettings.Default.StandbyValidators[0]).ScriptHash.ToArray(); snapshot.Add(CreateStorageKey(20, from_Account), new StorageItem(new NeoAccountState())); var accountState = snapshot.TryGet(CreateStorageKey(20, from_Account)).GetInteroperable(); accountState.Balance = 100; @@ -175,17 +186,19 @@ public void Check_Vote_VoteToNull() var persistingBlock = new Block { Header = new Header { Index = 1000 } }; var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); snapshot.Add(storageKey, new StorageItem(new HashIndexState { Hash = UInt256.Zero, Index = persistingBlock.Index - 1 })); - byte[] from = ProtocolSettings.Default.StandbyValidators[0].ToArray(); - var from_Account = Contract.CreateSignatureContract(ProtocolSettings.Default.StandbyValidators[0]).ScriptHash.ToArray(); + byte[] from = TestProtocolSettings.Default.StandbyValidators[0].ToArray(); + var from_Account = Contract.CreateSignatureContract(TestProtocolSettings.Default.StandbyValidators[0]).ScriptHash.ToArray(); snapshot.Add(CreateStorageKey(20, from_Account), new StorageItem(new NeoAccountState())); var accountState = snapshot.TryGet(CreateStorageKey(20, from_Account)).GetInteroperable(); accountState.Balance = 100; snapshot.Add(CreateStorageKey(33, ECCurve.Secp256r1.G.ToArray()), new StorageItem(new CandidateState() { Registered = true })); + snapshot.Add(CreateStorageKey(23, ECCurve.Secp256r1.G.ToArray()), new StorageItem(new BigInteger(100500))); var ret = Check_Vote(snapshot, from_Account, ECCurve.Secp256r1.G.ToArray(), true, persistingBlock); ret.Result.Should().BeTrue(); ret.State.Should().BeTrue(); accountState = snapshot.TryGet(CreateStorageKey(20, from_Account)).GetInteroperable(); accountState.VoteTo.Should().Be(ECCurve.Secp256r1.G); + accountState.LastGasPerVote.Should().Be(100500); //from vote to null account G votes becomes 0 var G_stateValidator = snapshot.GetAndChange(CreateStorageKey(33, ECCurve.Secp256r1.G.ToArray())).GetInteroperable(); @@ -200,6 +213,7 @@ public void Check_Vote_VoteToNull() G_stateValidator.Votes.Should().Be(0); accountState = snapshot.TryGet(CreateStorageKey(20, from_Account)).GetInteroperable(); accountState.VoteTo.Should().Be(null); + accountState.LastGasPerVote.Should().Be(0); } [TestMethod] @@ -211,7 +225,7 @@ public void Check_UnclaimedGas() var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); snapshot.Add(storageKey, new StorageItem(new HashIndexState { Hash = UInt256.Zero, Index = persistingBlock.Index - 1 })); - byte[] from = Contract.GetBFTAddress(ProtocolSettings.Default.StandbyValidators).ToArray(); + byte[] from = Contract.GetBFTAddress(TestProtocolSettings.Default.StandbyValidators).ToArray(); var unclaim = Check_UnclaimedGas(snapshot, from, persistingBlock); unclaim.Value.Should().Be(new BigInteger(0.5 * 1000 * 100000000L)); @@ -228,7 +242,7 @@ public void Check_RegisterValidator() var snapshot = _snapshot.CreateSnapshot(); var keyCount = snapshot.GetChangeSet().Count(); - var point = ProtocolSettings.Default.StandbyValidators[0].EncodePoint(true).Clone() as byte[]; + var point = TestProtocolSettings.Default.StandbyValidators[0].EncodePoint(true).Clone() as byte[]; var ret = Check_RegisterValidator(snapshot, point, _persistingBlock); // Exists ret.State.Should().BeTrue(); @@ -256,7 +270,7 @@ public void Check_UnregisterCandidate() var snapshot = _snapshot.CreateSnapshot(); _persistingBlock.Header.Index = 1; var keyCount = snapshot.GetChangeSet().Count(); - var point = ProtocolSettings.Default.StandbyValidators[0].EncodePoint(true); + var point = TestProtocolSettings.Default.StandbyValidators[0].EncodePoint(true); //without register var ret = Check_UnregisterCandidate(snapshot, point, _persistingBlock); @@ -293,7 +307,7 @@ public void Check_UnregisterCandidate() snapshot.Add(CreateStorageKey(20, G_Account), new StorageItem(new NeoAccountState())); var accountState = snapshot.TryGet(CreateStorageKey(20, G_Account)).GetInteroperable(); accountState.Balance = 100; - Check_Vote(snapshot, G_Account, ProtocolSettings.Default.StandbyValidators[0].ToArray(), true, _persistingBlock); + Check_Vote(snapshot, G_Account, TestProtocolSettings.Default.StandbyValidators[0].ToArray(), true, _persistingBlock); ret = Check_UnregisterCandidate(snapshot, point, _persistingBlock); ret.State.Should().BeTrue(); ret.Result.Should().BeTrue(); @@ -304,11 +318,11 @@ public void Check_UnregisterCandidate() pointState.Votes.Should().Be(100); //vote fail - ret = Check_Vote(snapshot, G_Account, ProtocolSettings.Default.StandbyValidators[0].ToArray(), true, _persistingBlock); + ret = Check_Vote(snapshot, G_Account, TestProtocolSettings.Default.StandbyValidators[0].ToArray(), true, _persistingBlock); ret.State.Should().BeTrue(); ret.Result.Should().BeFalse(); accountState = snapshot.TryGet(CreateStorageKey(20, G_Account)).GetInteroperable(); - accountState.VoteTo.Should().Be(ProtocolSettings.Default.StandbyValidators[0]); + accountState.VoteTo.Should().Be(TestProtocolSettings.Default.StandbyValidators[0]); } [TestMethod] @@ -316,7 +330,7 @@ public void Check_GetCommittee() { var snapshot = _snapshot.CreateSnapshot(); var keyCount = snapshot.GetChangeSet().Count(); - var point = ProtocolSettings.Default.StandbyValidators[0].EncodePoint(true); + var point = TestProtocolSettings.Default.StandbyValidators[0].EncodePoint(true); var persistingBlock = _persistingBlock; persistingBlock.Header.Index = 1; //register with votes with 20000000 @@ -332,9 +346,9 @@ public void Check_GetCommittee() ret.Result.Should().BeTrue(); var committeemembers = NativeContract.NEO.GetCommittee(snapshot); - var defaultCommittee = ProtocolSettings.Default.StandbyCommittee.OrderBy(p => p).ToArray(); + var defaultCommittee = TestProtocolSettings.Default.StandbyCommittee.OrderBy(p => p).ToArray(); committeemembers.GetType().Should().Be(typeof(ECPoint[])); - for (int i = 0; i < ProtocolSettings.Default.CommitteeMembersCount; i++) + for (int i = 0; i < TestProtocolSettings.Default.CommitteeMembersCount; i++) { committeemembers[i].Should().Be(defaultCommittee[i]); } @@ -344,7 +358,7 @@ public void Check_GetCommittee() { Header = new Header { - Index = (uint)ProtocolSettings.Default.CommitteeMembersCount, + Index = (uint)TestProtocolSettings.Default.CommitteeMembersCount, MerkleRoot = UInt256.Zero, NextConsensus = UInt160.Zero, PrevHash = UInt256.Zero, @@ -352,9 +366,9 @@ public void Check_GetCommittee() }, Transactions = Array.Empty() }; - for (int i = 0; i < ProtocolSettings.Default.CommitteeMembersCount - 1; i++) + for (int i = 0; i < TestProtocolSettings.Default.CommitteeMembersCount - 1; i++) { - ret = Check_RegisterValidator(snapshot, ProtocolSettings.Default.StandbyCommittee[i].ToArray(), persistingBlock); + ret = Check_RegisterValidator(snapshot, TestProtocolSettings.Default.StandbyCommittee[i].ToArray(), persistingBlock); ret.State.Should().BeTrue(); ret.Result.Should().BeTrue(); } @@ -362,13 +376,13 @@ public void Check_GetCommittee() Check_OnPersist(snapshot, persistingBlock).Should().BeTrue(); committeemembers = NativeContract.NEO.GetCommittee(snapshot); - committeemembers.Length.Should().Be(ProtocolSettings.Default.CommitteeMembersCount); + committeemembers.Length.Should().Be(TestProtocolSettings.Default.CommitteeMembersCount); committeemembers.Contains(ECCurve.Secp256r1.G).Should().BeTrue(); - for (int i = 0; i < ProtocolSettings.Default.CommitteeMembersCount - 1; i++) + for (int i = 0; i < TestProtocolSettings.Default.CommitteeMembersCount - 1; i++) { - committeemembers.Contains(ProtocolSettings.Default.StandbyCommittee[i]).Should().BeTrue(); + committeemembers.Contains(TestProtocolSettings.Default.StandbyCommittee[i]).Should().BeTrue(); } - committeemembers.Contains(ProtocolSettings.Default.StandbyCommittee[ProtocolSettings.Default.CommitteeMembersCount - 1]).Should().BeFalse(); + committeemembers.Contains(TestProtocolSettings.Default.StandbyCommittee[TestProtocolSettings.Default.CommitteeMembersCount - 1]).Should().BeFalse(); } [TestMethod] @@ -377,7 +391,7 @@ public void Check_Transfer() var snapshot = _snapshot.CreateSnapshot(); var persistingBlock = new Block { Header = new Header { Index = 1000 } }; - byte[] from = Contract.GetBFTAddress(ProtocolSettings.Default.StandbyValidators).ToArray(); + byte[] from = Contract.GetBFTAddress(TestProtocolSettings.Default.StandbyValidators).ToArray(); byte[] to = new byte[20]; var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); @@ -434,7 +448,7 @@ public void Check_Transfer() public void Check_BalanceOf() { var snapshot = _snapshot.CreateSnapshot(); - byte[] account = Contract.GetBFTAddress(ProtocolSettings.Default.StandbyValidators).ToArray(); + byte[] account = Contract.GetBFTAddress(TestProtocolSettings.Default.StandbyValidators).ToArray(); NativeContract.NEO.BalanceOf(snapshot, account).Should().Be(100_000_000); @@ -462,7 +476,7 @@ public void Check_CommitteeBonus() Check_PostPersist(snapshot, persistingBlock).Should().BeTrue(); - var committee = ProtocolSettings.Default.StandbyCommittee; + var committee = TestProtocolSettings.Default.StandbyCommittee; NativeContract.GAS.BalanceOf(snapshot, Contract.CreateSignatureContract(committee[0]).ScriptHash.ToArray()).Should().Be(50000000); NativeContract.GAS.BalanceOf(snapshot, Contract.CreateSignatureContract(committee[1]).ScriptHash.ToArray()).Should().Be(50000000); NativeContract.GAS.BalanceOf(snapshot, Contract.CreateSignatureContract(committee[2]).ScriptHash.ToArray()).Should().Be(0); @@ -545,9 +559,9 @@ public void TestCalculateBonus() snapshot.GetAndChange(key, () => new StorageItem(new NeoAccountState { Balance = 100, - VoteTo = ProtocolSettings.Default.StandbyCommittee[0] + VoteTo = TestProtocolSettings.Default.StandbyCommittee[0] })); - snapshot.Add(new KeyBuilder(NativeContract.NEO.Id, 23).Add(ProtocolSettings.Default.StandbyCommittee[0]).AddBigEndian(uint.MaxValue - 50), new StorageItem() { Value = new BigInteger(50 * 10000L).ToByteArray() }); + snapshot.Add(new KeyBuilder(NativeContract.NEO.Id, 23).Add(TestProtocolSettings.Default.StandbyCommittee[0]).AddBigEndian(uint.MaxValue - 50), new StorageItem() { Value = new BigInteger(50 * 10000L).ToByteArray() }); NativeContract.NEO.UnclaimedGas(snapshot, UInt160.Zero, 100).Should().Be(new BigInteger(50 * 100)); snapshot.Delete(key); } @@ -687,7 +701,7 @@ public void TestGetCommittee() public void TestGetValidators() { var snapshot = _snapshot.CreateSnapshot(); - var result = NativeContract.NEO.ComputeNextBlockValidators(snapshot, ProtocolSettings.Default); + var result = NativeContract.NEO.ComputeNextBlockValidators(snapshot, TestProtocolSettings.Default); result[0].ToArray().ToHexString().Should().Be("02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70"); result[1].ToArray().ToHexString().Should().Be("024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d"); result[2].ToArray().ToHexString().Should().Be("02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e"); @@ -759,9 +773,9 @@ public void TestClaimGas() // Initialize block snapshot.Add(CreateStorageKey(1), new StorageItem(new BigInteger(30000000))); - ECPoint[] standbyCommittee = ProtocolSettings.Default.StandbyCommittee.OrderBy(p => p).ToArray(); + ECPoint[] standbyCommittee = TestProtocolSettings.Default.StandbyCommittee.OrderBy(p => p).ToArray(); CachedCommittee cachedCommittee = new(); - for (var i = 0; i < ProtocolSettings.Default.CommitteeMembersCount; i++) + for (var i = 0; i < TestProtocolSettings.Default.CommitteeMembersCount; i++) { ECPoint member = standbyCommittee[i]; snapshot.Add(new KeyBuilder(NativeContract.NEO.Id, 33).Add(member), new StorageItem(new CandidateState() @@ -771,7 +785,7 @@ public void TestClaimGas() })); cachedCommittee.Add((member, 200 * 10000)); } - snapshot.GetOrAdd(new KeyBuilder(NativeContract.NEO.Id, 14), () => new StorageItem()).Value = BinarySerializer.Serialize(cachedCommittee.ToStackItem(null), 4096); + snapshot.GetOrAdd(new KeyBuilder(NativeContract.NEO.Id, 14), () => new StorageItem()).Value = BinarySerializer.Serialize(cachedCommittee.ToStackItem(null), ExecutionEngineLimits.Default); var item = snapshot.GetAndChange(new KeyBuilder(NativeContract.NEO.Id, 1), () => new StorageItem()); item.Value = ((BigInteger)2100 * 10000L).ToByteArray(); @@ -790,9 +804,9 @@ public void TestClaimGas() }; Check_PostPersist(snapshot, persistingBlock).Should().BeTrue(); - var committee = ProtocolSettings.Default.StandbyCommittee.OrderBy(p => p).ToArray(); + var committee = TestProtocolSettings.Default.StandbyCommittee.OrderBy(p => p).ToArray(); var accountA = committee[0]; - var accountB = committee[ProtocolSettings.Default.CommitteeMembersCount - 1]; + var accountB = committee[TestProtocolSettings.Default.CommitteeMembersCount - 1]; NativeContract.NEO.BalanceOf(snapshot, Contract.CreateSignatureContract(accountA).ScriptHash).Should().Be(0); StorageItem storageItem = snapshot.TryGet(new KeyBuilder(NativeContract.NEO.Id, 23).Add(accountA)); @@ -837,7 +851,7 @@ public void TestClaimGas() }; Check_PostPersist(snapshot, persistingBlock).Should().BeTrue(); - accountA = ProtocolSettings.Default.StandbyCommittee.OrderBy(p => p).ToArray()[2]; + accountA = TestProtocolSettings.Default.StandbyCommittee.OrderBy(p => p).ToArray()[2]; NativeContract.NEO.BalanceOf(snapshot, Contract.CreateSignatureContract(committee[2]).ScriptHash).Should().Be(0); storageItem = snapshot.TryGet(new KeyBuilder(NativeContract.NEO.Id, 23).Add(committee[2])); diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_PolicyContract.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_PolicyContract.cs index 6730dd8825..703253364c 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_PolicyContract.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_PolicyContract.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_PolicyContract.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; @@ -8,6 +19,7 @@ using Neo.UnitTests.Extensions; using System; using System.Linq; +using System.Numerics; namespace Neo.UnitTests.SmartContract.Native { @@ -22,7 +34,7 @@ public void TestSetup() _snapshot = TestBlockchain.GetTestSnapshot(); ApplicationEngine engine = ApplicationEngine.Create(TriggerType.OnPersist, null, _snapshot, new Block { Header = new Header() }, settings: TestBlockchain.TheNeoSystem.Settings, gas: 0); - NativeContract.ContractManagement.OnPersist(engine); + NativeContract.ContractManagement.OnPersistAsync(engine); } [TestMethod] @@ -33,6 +45,71 @@ public void Check_Default() var ret = NativeContract.Policy.Call(snapshot, "getFeePerByte"); ret.Should().BeOfType(); ret.GetInteger().Should().Be(1000); + + ret = NativeContract.Policy.Call(snapshot, "getAttributeFee", new ContractParameter(ContractParameterType.Integer) { Value = (BigInteger)(byte)TransactionAttributeType.Conflicts }); + ret.Should().BeOfType(); + ret.GetInteger().Should().Be(PolicyContract.DefaultAttributeFee); + + Assert.ThrowsException(() => NativeContract.Policy.Call(snapshot, "getAttributeFee", new ContractParameter(ContractParameterType.Integer) { Value = (BigInteger)byte.MaxValue })); + } + + [TestMethod] + public void Check_SetAttributeFee() + { + var snapshot = _snapshot.CreateSnapshot(); + + // Fake blockchain + Block block = new() + { + Header = new Header + { + Index = 1000, + PrevHash = UInt256.Zero + } + }; + + var attr = new ContractParameter(ContractParameterType.Integer) { Value = (BigInteger)(byte)TransactionAttributeType.Conflicts }; + + // Without signature + Assert.ThrowsException(() => + { + NativeContract.Policy.Call(snapshot, new Nep17NativeContractExtensions.ManualWitness(), block, + "setAttributeFee", attr, new ContractParameter(ContractParameterType.Integer) { Value = 100500 }); + }); + + var ret = NativeContract.Policy.Call(snapshot, "getAttributeFee", attr); + ret.Should().BeOfType(); + ret.GetInteger().Should().Be(0); + + // With signature, wrong value + UInt160 committeeMultiSigAddr = NativeContract.NEO.GetCommitteeAddress(snapshot); + Assert.ThrowsException(() => + { + NativeContract.Policy.Call(snapshot, new Nep17NativeContractExtensions.ManualWitness(committeeMultiSigAddr), block, + "setAttributeFee", attr, new ContractParameter(ContractParameterType.Integer) { Value = 11_0000_0000 }); + }); + + ret = NativeContract.Policy.Call(snapshot, "getAttributeFee", attr); + ret.Should().BeOfType(); + ret.GetInteger().Should().Be(0); + + // Proper set + ret = NativeContract.Policy.Call(snapshot, new Nep17NativeContractExtensions.ManualWitness(committeeMultiSigAddr), block, + "setAttributeFee", attr, new ContractParameter(ContractParameterType.Integer) { Value = 300300 }); + ret.IsNull.Should().BeTrue(); + + ret = NativeContract.Policy.Call(snapshot, "getAttributeFee", attr); + ret.Should().BeOfType(); + ret.GetInteger().Should().Be(300300); + + // Set to zero + ret = NativeContract.Policy.Call(snapshot, new Nep17NativeContractExtensions.ManualWitness(committeeMultiSigAddr), block, + "setAttributeFee", attr, new ContractParameter(ContractParameterType.Integer) { Value = 0 }); + ret.IsNull.Should().BeTrue(); + + ret = NativeContract.Policy.Call(snapshot, "getAttributeFee", attr); + ret.Should().BeOfType(); + ret.GetInteger().Should().Be(0); } [TestMethod] diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_RoleManagement.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_RoleManagement.cs index 45a0a0ddb6..25ca7ee0d6 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_RoleManagement.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_RoleManagement.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_RoleManagement.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; @@ -7,6 +18,7 @@ using Neo.SmartContract; using Neo.SmartContract.Native; using Neo.UnitTests.Extensions; +using Neo.Wallets; using System; using System.Collections.Generic; using System.Linq; @@ -25,52 +37,69 @@ public void TestSetup() _snapshot = TestBlockchain.GetTestSnapshot(); } + [TestCleanup] + public void Clean() + { + TestBlockchain.ResetStore(); + } + [TestMethod] public void TestSetAndGet() { - var snapshot1 = _snapshot.CreateSnapshot(); - UInt160 committeeMultiSigAddr = NativeContract.NEO.GetCommitteeAddress(snapshot1); - ECPoint[] validators = NativeContract.NEO.ComputeNextBlockValidators(snapshot1, ProtocolSettings.Default); - List notifications = new List(); - EventHandler ev = (o, e) => notifications.Add(e); - ApplicationEngine.Notify += ev; - var ret = NativeContract.RoleManagement.Call( - snapshot1, - new Nep17NativeContractExtensions.ManualWitness(committeeMultiSigAddr), - new Block { Header = new Header() }, - "designateAsRole", - new ContractParameter(ContractParameterType.Integer) { Value = new BigInteger((int)Role.StateValidator) }, - new ContractParameter(ContractParameterType.Array) { Value = validators.Select(p => new ContractParameter(ContractParameterType.ByteArray) { Value = p.ToArray() }).ToList() } - ); - snapshot1.Commit(); - ApplicationEngine.Notify -= ev; - notifications.Count.Should().Be(1); - notifications[0].EventName.Should().Be("Designation"); - var snapshot2 = _snapshot.CreateSnapshot(); - ret = NativeContract.RoleManagement.Call( - snapshot2, - "getDesignatedByRole", - new ContractParameter(ContractParameterType.Integer) { Value = new BigInteger((int)Role.StateValidator) }, - new ContractParameter(ContractParameterType.Integer) { Value = new BigInteger(1u) } - ); - ret.Should().BeOfType(); - (ret as VM.Types.Array).Count.Should().Be(7); - (ret as VM.Types.Array)[0].GetSpan().ToHexString().Should().Be(validators[0].ToArray().ToHexString()); - (ret as VM.Types.Array)[1].GetSpan().ToHexString().Should().Be(validators[1].ToArray().ToHexString()); - (ret as VM.Types.Array)[2].GetSpan().ToHexString().Should().Be(validators[2].ToArray().ToHexString()); - (ret as VM.Types.Array)[3].GetSpan().ToHexString().Should().Be(validators[3].ToArray().ToHexString()); - (ret as VM.Types.Array)[4].GetSpan().ToHexString().Should().Be(validators[4].ToArray().ToHexString()); - (ret as VM.Types.Array)[5].GetSpan().ToHexString().Should().Be(validators[5].ToArray().ToHexString()); - (ret as VM.Types.Array)[6].GetSpan().ToHexString().Should().Be(validators[6].ToArray().ToHexString()); + byte[] privateKey1 = new byte[32]; + var rng1 = System.Security.Cryptography.RandomNumberGenerator.Create(); + rng1.GetBytes(privateKey1); + KeyPair key1 = new KeyPair(privateKey1); + byte[] privateKey2 = new byte[32]; + var rng2 = System.Security.Cryptography.RandomNumberGenerator.Create(); + rng2.GetBytes(privateKey2); + KeyPair key2 = new KeyPair(privateKey2); + ECPoint[] publicKeys = new ECPoint[2]; + publicKeys[0] = key1.PublicKey; + publicKeys[1] = key2.PublicKey; + publicKeys = publicKeys.OrderBy(p => p).ToArray(); + + List roles = new List() { Role.StateValidator, Role.Oracle, Role.NeoFSAlphabetNode, Role.P2PNotary }; + foreach (var role in roles) + { + var snapshot1 = _snapshot.CreateSnapshot(); + UInt160 committeeMultiSigAddr = NativeContract.NEO.GetCommitteeAddress(snapshot1); + List notifications = new List(); + EventHandler ev = (o, e) => notifications.Add(e); + ApplicationEngine.Notify += ev; + var ret = NativeContract.RoleManagement.Call( + snapshot1, + new Nep17NativeContractExtensions.ManualWitness(committeeMultiSigAddr), + new Block { Header = new Header() }, + "designateAsRole", + new ContractParameter(ContractParameterType.Integer) { Value = new BigInteger((int)role) }, + new ContractParameter(ContractParameterType.Array) { Value = publicKeys.Select(p => new ContractParameter(ContractParameterType.ByteArray) { Value = p.ToArray() }).ToList() } + ); + snapshot1.Commit(); + ApplicationEngine.Notify -= ev; + notifications.Count.Should().Be(1); + notifications[0].EventName.Should().Be("Designation"); + var snapshot2 = _snapshot.CreateSnapshot(); + ret = NativeContract.RoleManagement.Call( + snapshot2, + "getDesignatedByRole", + new ContractParameter(ContractParameterType.Integer) { Value = new BigInteger((int)role) }, + new ContractParameter(ContractParameterType.Integer) { Value = new BigInteger(1u) } + ); + ret.Should().BeOfType(); + (ret as VM.Types.Array).Count.Should().Be(2); + (ret as VM.Types.Array)[0].GetSpan().ToHexString().Should().Be(publicKeys[0].ToArray().ToHexString()); + (ret as VM.Types.Array)[1].GetSpan().ToHexString().Should().Be(publicKeys[1].ToArray().ToHexString()); - ret = NativeContract.RoleManagement.Call( - snapshot2, - "getDesignatedByRole", - new ContractParameter(ContractParameterType.Integer) { Value = new BigInteger((int)Role.StateValidator) }, - new ContractParameter(ContractParameterType.Integer) { Value = new BigInteger(0) } - ); - ret.Should().BeOfType(); - (ret as VM.Types.Array).Count.Should().Be(0); + ret = NativeContract.RoleManagement.Call( + snapshot2, + "getDesignatedByRole", + new ContractParameter(ContractParameterType.Integer) { Value = new BigInteger((int)role) }, + new ContractParameter(ContractParameterType.Integer) { Value = new BigInteger(0) } + ); + ret.Should().BeOfType(); + (ret as VM.Types.Array).Count.Should().Be(0); + } } private void ApplicationEngine_Notify(object sender, NotifyEventArgs e) diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_StdLib.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_StdLib.cs index 8342e15824..f36fa2cfa4 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_StdLib.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_StdLib.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_StdLib.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.SmartContract; using Neo.SmartContract.Native; @@ -209,6 +220,47 @@ public void StringSplit() Assert.AreEqual("b", arr[1].GetString()); } + [TestMethod] + public void StringElementLength() + { + var snapshot = TestBlockchain.GetTestSnapshot(); + + using var script = new ScriptBuilder(); + script.EmitDynamicCall(NativeContract.StdLib.Hash, "strLen", "🦆"); + script.EmitDynamicCall(NativeContract.StdLib.Hash, "strLen", "ã"); + script.EmitDynamicCall(NativeContract.StdLib.Hash, "strLen", "a"); + + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + engine.LoadScript(script.ToArray()); + + Assert.AreEqual(engine.Execute(), VMState.HALT); + Assert.AreEqual(3, engine.ResultStack.Count); + Assert.AreEqual(1, engine.ResultStack.Pop().GetInteger()); + Assert.AreEqual(1, engine.ResultStack.Pop().GetInteger()); + Assert.AreEqual(1, engine.ResultStack.Pop().GetInteger()); + } + + [TestMethod] + public void TestInvalidUtf8Sequence() + { + // Simulating invalid UTF-8 byte (0xff) decoded as a UTF-16 char + const char badChar = (char)0xff; + var badStr = badChar.ToString(); + var snapshot = TestBlockchain.GetTestSnapshot(); + + using var script = new ScriptBuilder(); + script.EmitDynamicCall(NativeContract.StdLib.Hash, "strLen", badStr); + script.EmitDynamicCall(NativeContract.StdLib.Hash, "strLen", badStr + "ab"); + + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + engine.LoadScript(script.ToArray()); + + Assert.AreEqual(engine.Execute(), VMState.HALT); + Assert.AreEqual(2, engine.ResultStack.Count); + Assert.AreEqual(3, engine.ResultStack.Pop().GetInteger()); + Assert.AreEqual(1, engine.ResultStack.Pop().GetInteger()); + } + [TestMethod] public void Json_Deserialize() { diff --git a/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.Contract.cs b/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.Contract.cs index 939cc282a2..f9dc5e7b46 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.Contract.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.Contract.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ApplicationEngine.Contract.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.SmartContract; @@ -11,8 +22,8 @@ public partial class UT_ApplicationEngine [TestMethod] public void TestCreateStandardAccount() { - var settings = ProtocolSettings.Default; - using var engine = ApplicationEngine.Create(TriggerType.Application, null, null, settings: TestBlockchain.TheNeoSystem.Settings, gas: 1100_00000000); + var settings = TestProtocolSettings.Default; + using var engine = ApplicationEngine.Create(TriggerType.Application, null, null, settings: TestProtocolSettings.Default, gas: 1100_00000000); using var script = new ScriptBuilder(); script.EmitSysCall(ApplicationEngine.System_Contract_CreateStandardAccount, settings.StandbyCommittee[0].EncodePoint(true)); @@ -27,7 +38,7 @@ public void TestCreateStandardAccount() [TestMethod] public void TestCreateStandardMultisigAccount() { - var settings = ProtocolSettings.Default; + var settings = TestProtocolSettings.Default; using var engine = ApplicationEngine.Create(TriggerType.Application, null, null, settings: TestBlockchain.TheNeoSystem.Settings, gas: 1100_00000000); using var script = new ScriptBuilder(); diff --git a/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.Runtime.cs b/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.Runtime.cs index 7d4faee5b5..75e6e7b6da 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.Runtime.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.Runtime.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ApplicationEngine.Runtime.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Network.P2P.Payloads; @@ -164,5 +175,17 @@ public void TestGetRandomDifferentBlock() rand_4.Should().NotBe(rand_9); rand_5.Should().NotBe(rand_10); } + + [TestMethod] + public void TestInvalidUtf8LogMessage() + { + var tx_1 = TestUtils.GetTransaction(UInt160.Zero); + using var engine = ApplicationEngine.Create(TriggerType.Application, tx_1, null, TestBlockchain.TheNeoSystem.GenesisBlock, settings: TestBlockchain.TheNeoSystem.Settings, gas: 1100_00000000); + var msg = new byte[] + { + 68, 216, 160, 6, 89, 102, 86, 72, 37, 15, 132, 45, 76, 221, 170, 21, 128, 51, 34, 168, 205, 56, 10, 228, 51, 114, 4, 218, 245, 155, 172, 132 + }; + Assert.ThrowsException(() => engine.RuntimeLog(msg)); + } } } diff --git a/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.cs b/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.cs index 3687f0605e..0969e12776 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.cs @@ -1,9 +1,23 @@ -using System; -using System.Collections.Immutable; -using System.Linq; +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ApplicationEngine.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.SmartContract; +using Neo.SmartContract.Manifest; +using Neo.UnitTests.Extensions; +using Neo.VM; +using System; +using System.Collections.Immutable; +using System.Linq; using Array = Neo.VM.Types.Array; namespace Neo.UnitTests.SmartContract @@ -54,7 +68,7 @@ public void TestCreateDummyBlock() { var snapshot = TestBlockchain.GetTestSnapshot(); byte[] SyscallSystemRuntimeCheckWitnessHash = new byte[] { 0x68, 0xf8, 0x27, 0xec, 0x8c }; - ApplicationEngine engine = ApplicationEngine.Run(SyscallSystemRuntimeCheckWitnessHash, snapshot); + ApplicationEngine engine = ApplicationEngine.Run(SyscallSystemRuntimeCheckWitnessHash, snapshot, settings: TestProtocolSettings.Default); engine.PersistingBlock.Version.Should().Be(0); engine.PersistingBlock.PrevHash.Should().Be(TestBlockchain.TheNeoSystem.GenesisBlock.Hash); engine.PersistingBlock.MerkleRoot.Should().Be(new UInt256()); @@ -92,5 +106,103 @@ public void TestCheckingHardfork() (setting[sortedHardforks[i]] > setting[sortedHardforks[i + 1]]).Should().Be(false); } } + + [TestMethod] + public void TestSystem_Contract_Call_Permissions() + { + UInt160 scriptHash; + var snapshot = TestBlockchain.GetTestSnapshot(); + + // Setup: put a simple contract to the storage. + using (var script = new ScriptBuilder()) + { + // Push True on stack and return. + script.EmitPush(true); + script.Emit(OpCode.RET); + + // Mock contract and put it to the Managemant's storage. + scriptHash = script.ToArray().ToScriptHash(); + + snapshot.DeleteContract(scriptHash); + var contract = TestUtils.GetContract(script.ToArray(), TestUtils.CreateManifest("test", ContractParameterType.Any)); + contract.Manifest.Abi.Methods = new[] + { + new ContractMethodDescriptor + { + Name = "disallowed", + Parameters = new ContractParameterDefinition[]{} + }, + new ContractMethodDescriptor + { + Name = "test", + Parameters = new ContractParameterDefinition[]{} + } + }; + snapshot.AddContract(scriptHash, contract); + } + + // Disallowed method call. + using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, null, ProtocolSettings.Default)) + using (var script = new ScriptBuilder()) + { + // Build call script calling disallowed method. + script.EmitDynamicCall(scriptHash, "disallowed"); + + // Mock executing state to be a contract-based. + engine.LoadScript(script.ToArray()); + engine.CurrentContext.GetState().Contract = new() + { + Manifest = new() + { + Abi = new() { }, + Permissions = new ContractPermission[] + { + new ContractPermission + { + Contract = ContractPermissionDescriptor.Create(scriptHash), + Methods = WildcardContainer.Create(new string[]{"test"}) // allowed to call only "test" method of the target contract. + } + } + } + }; + var currentScriptHash = engine.EntryScriptHash; + + Assert.AreEqual(VMState.FAULT, engine.Execute()); + Assert.IsTrue(engine.FaultException.ToString().Contains($"Cannot Call Method disallowed Of Contract {scriptHash.ToString()}")); + } + + // Allowed method call. + using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, null, ProtocolSettings.Default)) + using (var script = new ScriptBuilder()) + { + // Build call script. + script.EmitDynamicCall(scriptHash, "test"); + + // Mock executing state to be a contract-based. + engine.LoadScript(script.ToArray()); + engine.CurrentContext.GetState().Contract = new() + { + Manifest = new() + { + Abi = new() { }, + Permissions = new ContractPermission[] + { + new ContractPermission + { + Contract = ContractPermissionDescriptor.Create(scriptHash), + Methods = WildcardContainer.Create(new string[]{"test"}) // allowed to call only "test" method of the target contract. + } + } + } + }; + var currentScriptHash = engine.EntryScriptHash; + + Assert.AreEqual(VMState.HALT, engine.Execute()); + Assert.AreEqual(1, engine.ResultStack.Count); + Assert.IsInstanceOfType(engine.ResultStack.Peek(), typeof(VM.Types.Boolean)); + var res = (VM.Types.Boolean)engine.ResultStack.Pop(); + Assert.IsTrue(res.GetBoolean()); + } + } } } diff --git a/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngineProvider.cs b/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngineProvider.cs index bcac4305c0..75fc558669 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngineProvider.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngineProvider.cs @@ -1,8 +1,20 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ApplicationEngineProvider.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Network.P2P.Payloads; using Neo.Persistence; using Neo.SmartContract; +using Neo.VM; namespace Neo.UnitTests.SmartContract { @@ -39,16 +51,16 @@ public void TestDefaultAppEngineProvider() class TestProvider : IApplicationEngineProvider { - public ApplicationEngine Create(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock, ProtocolSettings settings, long gas, IDiagnostic diagnostic) + public ApplicationEngine Create(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock, ProtocolSettings settings, long gas, IDiagnostic diagnostic, JumpTable jumpTable) { - return new TestEngine(trigger, container, snapshot, persistingBlock, settings, gas, diagnostic); + return new TestEngine(trigger, container, snapshot, persistingBlock, settings, gas, diagnostic, jumpTable); } } class TestEngine : ApplicationEngine { - public TestEngine(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock, ProtocolSettings settings, long gas, IDiagnostic diagnostic) - : base(trigger, container, snapshot, persistingBlock, settings, gas, diagnostic) + public TestEngine(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock, ProtocolSettings settings, long gas, IDiagnostic diagnostic, JumpTable jumpTable) + : base(trigger, container, snapshot, persistingBlock, settings, gas, diagnostic, jumpTable) { } } diff --git a/tests/Neo.UnitTests/SmartContract/UT_BinarySerializer.cs b/tests/Neo.UnitTests/SmartContract/UT_BinarySerializer.cs index b3f30e0cee..6202c8336b 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_BinarySerializer.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_BinarySerializer.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_BinarySerializer.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.SmartContract; @@ -13,36 +24,34 @@ namespace Neo.UnitTests.SmartContract [TestClass] public class UT_BinarySerializer { - private const int MaxItemSize = 1024 * 1024; - [TestMethod] public void TestSerialize() { - byte[] result1 = BinarySerializer.Serialize(new byte[5], MaxItemSize); + byte[] result1 = BinarySerializer.Serialize(new byte[5], ExecutionEngineLimits.Default); byte[] expectedArray1 = new byte[] { 0x28, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00 }; Assert.AreEqual(Encoding.Default.GetString(expectedArray1), Encoding.Default.GetString(result1)); - byte[] result2 = BinarySerializer.Serialize(true, MaxItemSize); + byte[] result2 = BinarySerializer.Serialize(true, ExecutionEngineLimits.Default); byte[] expectedArray2 = new byte[] { 0x20, 0x01 }; Assert.AreEqual(Encoding.Default.GetString(expectedArray2), Encoding.Default.GetString(result2)); - byte[] result3 = BinarySerializer.Serialize(1, MaxItemSize); + byte[] result3 = BinarySerializer.Serialize(1, ExecutionEngineLimits.Default); byte[] expectedArray3 = new byte[] { 0x21, 0x01, 0x01 }; Assert.AreEqual(Encoding.Default.GetString(expectedArray3), Encoding.Default.GetString(result3)); StackItem stackItem4 = new InteropInterface(new object()); - Action action4 = () => BinarySerializer.Serialize(stackItem4, MaxItemSize); + Action action4 = () => BinarySerializer.Serialize(stackItem4, ExecutionEngineLimits.Default); action4.Should().Throw(); List list6 = new List { 1 }; StackItem stackItem62 = new VM.Types.Array(list6); - byte[] result6 = BinarySerializer.Serialize(stackItem62, MaxItemSize); + byte[] result6 = BinarySerializer.Serialize(stackItem62, ExecutionEngineLimits.Default); byte[] expectedArray6 = new byte[] { 0x40,0x01,0x21,0x01,0x01 }; @@ -50,14 +59,14 @@ public void TestSerialize() List list7 = new List { 1 }; StackItem stackItem72 = new Struct(list7); - byte[] result7 = BinarySerializer.Serialize(stackItem72, MaxItemSize); + byte[] result7 = BinarySerializer.Serialize(stackItem72, ExecutionEngineLimits.Default); byte[] expectedArray7 = new byte[] { 0x41,0x01,0x21,0x01,0x01 }; Assert.AreEqual(Encoding.Default.GetString(expectedArray7), Encoding.Default.GetString(result7)); StackItem stackItem82 = new Map { [2] = 1 }; - byte[] result8 = BinarySerializer.Serialize(stackItem82, MaxItemSize); + byte[] result8 = BinarySerializer.Serialize(stackItem82, ExecutionEngineLimits.Default); byte[] expectedArray8 = new byte[] { 0x48,0x01,0x21,0x01,0x02,0x21,0x01,0x01 }; @@ -65,12 +74,12 @@ public void TestSerialize() Map stackItem91 = new Map(); stackItem91[1] = stackItem91; - Action action9 = () => BinarySerializer.Serialize(stackItem91, MaxItemSize); + Action action9 = () => BinarySerializer.Serialize(stackItem91, ExecutionEngineLimits.Default); action9.Should().Throw(); VM.Types.Array stackItem10 = new VM.Types.Array(); stackItem10.Add(stackItem10); - Action action10 = () => BinarySerializer.Serialize(stackItem10, MaxItemSize); + Action action10 = () => BinarySerializer.Serialize(stackItem10, ExecutionEngineLimits.Default); action10.Should().Throw(); } @@ -78,41 +87,41 @@ public void TestSerialize() public void TestDeserializeStackItem() { StackItem stackItem1 = new ByteString(new byte[5]); - byte[] byteArray1 = BinarySerializer.Serialize(stackItem1, MaxItemSize); + byte[] byteArray1 = BinarySerializer.Serialize(stackItem1, ExecutionEngineLimits.Default); StackItem result1 = BinarySerializer.Deserialize(byteArray1, ExecutionEngineLimits.Default); Assert.AreEqual(stackItem1, result1); StackItem stackItem2 = StackItem.True; - byte[] byteArray2 = BinarySerializer.Serialize(stackItem2, MaxItemSize); + byte[] byteArray2 = BinarySerializer.Serialize(stackItem2, ExecutionEngineLimits.Default); StackItem result2 = BinarySerializer.Deserialize(byteArray2, ExecutionEngineLimits.Default); Assert.AreEqual(stackItem2, result2); StackItem stackItem3 = new Integer(1); - byte[] byteArray3 = BinarySerializer.Serialize(stackItem3, MaxItemSize); + byte[] byteArray3 = BinarySerializer.Serialize(stackItem3, ExecutionEngineLimits.Default); StackItem result3 = BinarySerializer.Deserialize(byteArray3, ExecutionEngineLimits.Default); Assert.AreEqual(stackItem3, result3); - byte[] byteArray4 = BinarySerializer.Serialize(1, MaxItemSize); + byte[] byteArray4 = BinarySerializer.Serialize(1, ExecutionEngineLimits.Default); byteArray4[0] = 0x40; Action action4 = () => BinarySerializer.Deserialize(byteArray4, ExecutionEngineLimits.Default); action4.Should().Throw(); List list5 = new List { 1 }; StackItem stackItem52 = new VM.Types.Array(list5); - byte[] byteArray5 = BinarySerializer.Serialize(stackItem52, MaxItemSize); + byte[] byteArray5 = BinarySerializer.Serialize(stackItem52, ExecutionEngineLimits.Default); StackItem result5 = BinarySerializer.Deserialize(byteArray5, ExecutionEngineLimits.Default); Assert.AreEqual(((VM.Types.Array)stackItem52).Count, ((VM.Types.Array)result5).Count); Assert.AreEqual(((VM.Types.Array)stackItem52).GetEnumerator().Current, ((VM.Types.Array)result5).GetEnumerator().Current); List list6 = new List { 1 }; StackItem stackItem62 = new Struct(list6); - byte[] byteArray6 = BinarySerializer.Serialize(stackItem62, MaxItemSize); + byte[] byteArray6 = BinarySerializer.Serialize(stackItem62, ExecutionEngineLimits.Default); StackItem result6 = BinarySerializer.Deserialize(byteArray6, ExecutionEngineLimits.Default); Assert.AreEqual(((Struct)stackItem62).Count, ((Struct)result6).Count); Assert.AreEqual(((Struct)stackItem62).GetEnumerator().Current, ((Struct)result6).GetEnumerator().Current); StackItem stackItem72 = new Map { [2] = 1 }; - byte[] byteArray7 = BinarySerializer.Serialize(stackItem72, MaxItemSize); + byte[] byteArray7 = BinarySerializer.Serialize(stackItem72, ExecutionEngineLimits.Default); StackItem result7 = BinarySerializer.Deserialize(byteArray7, ExecutionEngineLimits.Default); Assert.AreEqual(((Map)stackItem72).Count, ((Map)result7).Count); CollectionAssert.AreEqual(((Map)stackItem72).Keys.ToArray(), ((Map)result7).Keys.ToArray()); diff --git a/tests/Neo.UnitTests/SmartContract/UT_Contract.cs b/tests/Neo.UnitTests/SmartContract/UT_Contract.cs index 1661203d82..07ae320a0f 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_Contract.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_Contract.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Contract.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Network.P2P.Payloads; @@ -153,13 +164,13 @@ public void TestSignatureRedeemScriptFee() byte[] verification = Contract.CreateSignatureRedeemScript(key.PublicKey); byte[] invocation = new ScriptBuilder().EmitPush(UInt160.Zero).ToArray(); - var fee = PolicyContract.DefaultExecFeeFactor * (ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] * 2 + ApplicationEngine.OpCodePrices[OpCode.SYSCALL] + ApplicationEngine.CheckSigPrice); + var fee = PolicyContract.DefaultExecFeeFactor * (ApplicationEngine.OpCodePriceTable[(byte)OpCode.PUSHDATA1] * 2 + ApplicationEngine.OpCodePriceTable[(byte)OpCode.SYSCALL] + ApplicationEngine.CheckSigPrice); using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, new Transaction { Signers = Array.Empty(), Attributes = Array.Empty() }, null, settings: TestBlockchain.TheNeoSystem.Settings)) { engine.LoadScript(invocation.Concat(verification).ToArray(), configureState: p => p.CallFlags = CallFlags.None); engine.Execute(); - engine.GasConsumed.Should().Be(fee); + engine.FeeConsumed.Should().Be(fee); } } @@ -181,13 +192,13 @@ public void TestCreateMultiSigRedeemScriptFee() byte[] verification = Contract.CreateMultiSigRedeemScript(2, publicKeys); byte[] invocation = new ScriptBuilder().EmitPush(UInt160.Zero).EmitPush(UInt160.Zero).ToArray(); - long fee = PolicyContract.DefaultExecFeeFactor * (ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] * (2 + 2) + ApplicationEngine.OpCodePrices[OpCode.PUSHINT8] * 2 + ApplicationEngine.OpCodePrices[OpCode.SYSCALL] + ApplicationEngine.CheckSigPrice * 2); + long fee = PolicyContract.DefaultExecFeeFactor * (ApplicationEngine.OpCodePriceTable[(byte)OpCode.PUSHDATA1] * (2 + 2) + ApplicationEngine.OpCodePriceTable[(byte)OpCode.PUSHINT8] * 2 + ApplicationEngine.OpCodePriceTable[(byte)OpCode.SYSCALL] + ApplicationEngine.CheckSigPrice * 2); using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, new Transaction { Signers = Array.Empty(), Attributes = Array.Empty() }, null, settings: TestBlockchain.TheNeoSystem.Settings)) { engine.LoadScript(invocation.Concat(verification).ToArray(), configureState: p => p.CallFlags = CallFlags.None); engine.Execute(); - engine.GasConsumed.Should().Be(fee); + engine.FeeConsumed.Should().Be(fee); } } } diff --git a/tests/Neo.UnitTests/SmartContract/UT_ContractParameter.cs b/tests/Neo.UnitTests/SmartContract/UT_ContractParameter.cs index 6a6db9e8dd..6618a918e7 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_ContractParameter.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_ContractParameter.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ContractParameter.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; @@ -176,8 +187,10 @@ public void TestToString() ContractParameter contractParameter1 = new(); Assert.AreEqual("(null)", contractParameter1.ToString()); - ContractParameter contractParameter2 = new(ContractParameterType.ByteArray); - contractParameter2.Value = new byte[1]; + ContractParameter contractParameter2 = new(ContractParameterType.ByteArray) + { + Value = new byte[1] + }; Assert.AreEqual("00", contractParameter2.ToString()); ContractParameter contractParameter3 = new(ContractParameterType.Array); diff --git a/tests/Neo.UnitTests/SmartContract/UT_ContractParameterContext.cs b/tests/Neo.UnitTests/SmartContract/UT_ContractParameterContext.cs index c8696cb437..aba90cc6f0 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_ContractParameterContext.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_ContractParameterContext.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ContractParameterContext.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; @@ -34,7 +45,7 @@ public void TestGetComplete() { var snapshot = TestBlockchain.GetTestSnapshot(); Transaction tx = TestUtils.GetTransaction(UInt160.Parse("0x1bd5c777ec35768892bd3daab60fb7a1cb905066")); - var context = new ContractParametersContext(snapshot, tx, ProtocolSettings.Default.Network); + var context = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); context.Completed.Should().BeFalse(); } @@ -43,17 +54,17 @@ public void TestToString() { var snapshot = TestBlockchain.GetTestSnapshot(); Transaction tx = TestUtils.GetTransaction(UInt160.Parse("0x1bd5c777ec35768892bd3daab60fb7a1cb905066")); - var context = new ContractParametersContext(snapshot, tx, ProtocolSettings.Default.Network); + var context = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); context.Add(contract, 0, new byte[] { 0x01 }); string str = context.ToString(); - str.Should().Be(@"{""type"":""Neo.Network.P2P.Payloads.Transaction"",""hash"":""0x602c1fa1c08b041e4e6b87aa9a9f9c643166cd34bdd5215a3dd85778c59cce88"",""data"":""AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFmUJDLobcPtqo9vZKIdjXsd8fVGwEAARI="",""items"":{},""network"":" + ProtocolSettings.Default.Network + "}"); + str.Should().Be(@"{""type"":""Neo.Network.P2P.Payloads.Transaction"",""hash"":""0x602c1fa1c08b041e4e6b87aa9a9f9c643166cd34bdd5215a3dd85778c59cce88"",""data"":""AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFmUJDLobcPtqo9vZKIdjXsd8fVGwEAARI="",""items"":{},""network"":" + TestProtocolSettings.Default.Network + "}"); } [TestMethod] public void TestParse() { var snapshot = TestBlockchain.GetTestSnapshot(); - var ret = ContractParametersContext.Parse("{\"type\":\"Neo.Network.P2P.Payloads.Transaction\",\"data\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFmUJDLobcPtqo9vZKIdjXsd8fVGwEAARI=\",\"items\":{\"0xbecaad15c0ea585211faf99738a4354014f177f2\":{\"script\":\"IQJv8DuUkkHOHa3UNRnmlg4KhbQaaaBcMoEDqivOFZTKFmh0dHaq\",\"parameters\":[{\"type\":\"Signature\",\"value\":\"AQ==\"}],\"signatures\":{\"03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c\":\"AQ==\"}}},\"network\":" + ProtocolSettings.Default.Network + "}", snapshot); + var ret = ContractParametersContext.Parse("{\"type\":\"Neo.Network.P2P.Payloads.Transaction\",\"data\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFmUJDLobcPtqo9vZKIdjXsd8fVGwEAARI=\",\"items\":{\"0xbecaad15c0ea585211faf99738a4354014f177f2\":{\"script\":\"IQJv8DuUkkHOHa3UNRnmlg4KhbQaaaBcMoEDqivOFZTKFmh0dHaq\",\"parameters\":[{\"type\":\"Signature\",\"value\":\"AQ==\"}],\"signatures\":{\"03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c\":\"AQ==\"}}},\"network\":" + TestProtocolSettings.Default.Network + "}", snapshot); ret.ScriptHashes[0].ToString().Should().Be("0x1bd5c777ec35768892bd3daab60fb7a1cb905066"); ((Transaction)ret.Verifiable).Script.Span.ToHexString().Should().Be(new byte[] { 18 }.ToHexString()); } @@ -71,11 +82,11 @@ public void TestAdd() { var snapshot = TestBlockchain.GetTestSnapshot(); Transaction tx = TestUtils.GetTransaction(UInt160.Zero); - var context1 = new ContractParametersContext(snapshot, tx, ProtocolSettings.Default.Network); + var context1 = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); context1.Add(contract, 0, new byte[] { 0x01 }).Should().BeFalse(); tx = TestUtils.GetTransaction(UInt160.Parse("0x902e0d38da5e513b6d07c1c55b85e77d3dce8063")); - var context2 = new ContractParametersContext(snapshot, tx, ProtocolSettings.Default.Network); + var context2 = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); context2.Add(contract, 0, new byte[] { 0x01 }).Should().BeTrue(); //test repeatlly createItem context2.Add(contract, 0, new byte[] { 0x01 }).Should().BeTrue(); @@ -86,7 +97,7 @@ public void TestGetParameter() { var snapshot = TestBlockchain.GetTestSnapshot(); Transaction tx = TestUtils.GetTransaction(UInt160.Parse("0x902e0d38da5e513b6d07c1c55b85e77d3dce8063")); - var context = new ContractParametersContext(snapshot, tx, ProtocolSettings.Default.Network); + var context = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); context.GetParameter(tx.Sender, 0).Should().BeNull(); context.Add(contract, 0, new byte[] { 0x01 }); @@ -99,7 +110,7 @@ public void TestGetWitnesses() { var snapshot = TestBlockchain.GetTestSnapshot(); Transaction tx = TestUtils.GetTransaction(UInt160.Parse("0x902e0d38da5e513b6d07c1c55b85e77d3dce8063")); - var context = new ContractParametersContext(snapshot, tx, ProtocolSettings.Default.Network); + var context = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); context.Add(contract, 0, new byte[] { 0x01 }); Witness[] witnesses = context.GetWitnesses(); witnesses.Length.Should().Be(1); @@ -116,12 +127,12 @@ public void TestAddSignature() //singleSign - var context = new ContractParametersContext(snapshot, tx, ProtocolSettings.Default.Network); + var context = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); context.AddSignature(contract, key.PublicKey, new byte[] { 0x01 }).Should().BeTrue(); var contract1 = Contract.CreateSignatureContract(key.PublicKey); contract1.ParameterList = Array.Empty(); - context = new ContractParametersContext(snapshot, tx, ProtocolSettings.Default.Network); + context = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); context.AddSignature(contract1, key.PublicKey, new byte[] { 0x01 }).Should().BeFalse(); contract1.ParameterList = new[] { ContractParameterType.Signature, ContractParameterType.Signature }; @@ -143,16 +154,16 @@ public void TestAddSignature() }); var multiSender = UInt160.Parse("0xf76b51bc6605ac3cfcd188173af0930507f51210"); tx = TestUtils.GetTransaction(multiSender); - context = new ContractParametersContext(snapshot, tx, ProtocolSettings.Default.Network); + context = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); context.AddSignature(multiSignContract, key.PublicKey, new byte[] { 0x01 }).Should().BeTrue(); context.AddSignature(multiSignContract, key2.PublicKey, new byte[] { 0x01 }).Should().BeTrue(); tx = TestUtils.GetTransaction(singleSender); - context = new ContractParametersContext(snapshot, tx, ProtocolSettings.Default.Network); + context = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); context.AddSignature(multiSignContract, key.PublicKey, new byte[] { 0x01 }).Should().BeFalse(); tx = TestUtils.GetTransaction(multiSender); - context = new ContractParametersContext(snapshot, tx, ProtocolSettings.Default.Network); + context = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); byte[] privateKey3 = new byte[] { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, diff --git a/tests/Neo.UnitTests/SmartContract/UT_ContractState.cs b/tests/Neo.UnitTests/SmartContract/UT_ContractState.cs index 4e1eab87fe..d0e52ae234 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_ContractState.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_ContractState.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ContractState.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Json; @@ -12,7 +23,7 @@ namespace Neo.UnitTests.SmartContract public class UT_ContractState { ContractState contract; - byte[] script = { 0x01 }; + readonly byte[] script = { 0x01 }; ContractManifest manifest; [TestInitialize] diff --git a/tests/Neo.UnitTests/SmartContract/UT_DeployedContract.cs b/tests/Neo.UnitTests/SmartContract/UT_DeployedContract.cs index b1571868bc..b5f8bbde0e 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_DeployedContract.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_DeployedContract.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_DeployedContract.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.SmartContract; using System; diff --git a/tests/Neo.UnitTests/SmartContract/UT_Helper.cs b/tests/Neo.UnitTests/SmartContract/UT_Helper.cs index 2c5eb02dff..e629a5908f 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_Helper.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_Helper.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Helper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; using Neo.Network.P2P.Payloads; @@ -6,6 +17,8 @@ using Neo.VM; using Neo.Wallets; using System; +using System.Collections.Generic; +using System.Linq; using static Neo.SmartContract.Helper; namespace Neo.UnitTests.SmartContract @@ -61,6 +74,47 @@ public void TestIsMultiSigContract() Assert.IsFalse(IsMultiSigContract(case2)); } + [TestMethod] + // TestIsMultiSigContract_WrongCurve checks that multisignature verification script based on points + // not from Secp256r1 curve fails IsMultiSigContract check without any exception. + public void TestIsMultiSigContract_WrongCurve() + { + // A set of points on Koblitz curve in uncompressed representation. One of the points + // (the first one) is specially selected, this point can't be restored on Secp256r1 + // from compressed form, whereas three other points can be restored on both Secp256r1 + // and Koblitz curves. + var pubs = new List() + { + ECPoint.Parse("047b4e72ae854b6a0955b3e02d92651ab7fa641a936066776ad438f95bb674a269a63ff98544691663d91a6cfcd215831f01bfb7a226363a6c5c67ef14541dba07", ECCurve.Secp256k1), + ECPoint.Parse("040486468683c112125978ffe876245b2006bfe739aca8539b67335079262cb27ad0dedc9e5583f99b61c6f46bf80b97eaec3654b87add0e5bd7106c69922a229d", ECCurve.Secp256k1), + ECPoint.Parse("040d26fc2ad3b1aae20f040b5f83380670f8ef5c2b2ac921ba3bdd79fd0af0525177715fd4370b1012ddd10579698d186ab342c223da3e884ece9cab9b6638c7bb", ECCurve.Secp256k1), + ECPoint.Parse("04a114d72fe2997cdac67427b6f39ea08ed46213c8bb6a461bbac2a6212cf43fb510f8adf59b0b087a7859f96d0288e5e94800eab8388f30f03f92b2e4d807dfce", ECCurve.Secp256k1) + }; + const int m = 3; + + var badScript = Contract.CreateMultiSigRedeemScript(m, pubs); + Assert.IsFalse(IsMultiSigContract(badScript, out _, out ECPoint[] _)); // enforce runtime point decoding by specifying ECPoint[] out variable. + Assert.IsTrue(IsMultiSigContract(badScript)); // this overload is unlucky since it doesn't perform ECPoint decoding. + + // Exclude the first special point and check one more time, both methods should return true. + var goodScript = Contract.CreateMultiSigRedeemScript(m, pubs.Skip(1).ToArray()); + Assert.IsTrue(IsMultiSigContract(goodScript, out _, out ECPoint[] _)); // enforce runtime point decoding by specifying ECPoint[] out variable. + Assert.IsTrue(IsMultiSigContract(goodScript)); // this overload is unlucky since it doesn't perform ECPoint decoding. + } + + [TestMethod] + // TestIsSignatureContract_WrongCurve checks that signature verification script based on point + // not from Secp256r1 curve passes IsSignatureContract check without any exception. + public void TestIsSignatureContract_WrongCurve() + { + // A special point on Koblitz curve that can't be restored at Secp256r1 from compressed form. + var pub = ECPoint.Parse("047b4e72ae854b6a0955b3e02d92651ab7fa641a936066776ad438f95bb674a269a63ff98544691663d91a6cfcd215831f01bfb7a226363a6c5c67ef14541dba07", ECCurve.Secp256k1); + var script = Contract.CreateSignatureRedeemScript(pub); + + // IsSignatureContract should pass since it doesn't perform ECPoint decoding. + Assert.IsTrue(IsSignatureContract(script)); + } + [TestMethod] public void TestSignatureContractCost() { @@ -70,16 +124,16 @@ public void TestSignatureContractCost() tx.Signers[0].Account = contract.ScriptHash; using ScriptBuilder invocationScript = new(); - invocationScript.EmitPush(Neo.Wallets.Helper.Sign(tx, _key, ProtocolSettings.Default.Network)); + invocationScript.EmitPush(Neo.Wallets.Helper.Sign(tx, _key, TestProtocolSettings.Default.Network)); tx.Witnesses = new Witness[] { new Witness() { InvocationScript = invocationScript.ToArray(), VerificationScript = contract.Script } }; - using var engine = ApplicationEngine.Create(TriggerType.Verification, tx, null, null, ProtocolSettings.Default); + using var engine = ApplicationEngine.Create(TriggerType.Verification, tx, null, null, TestProtocolSettings.Default); engine.LoadScript(contract.Script); engine.LoadScript(new Script(invocationScript.ToArray(), true), configureState: p => p.CallFlags = CallFlags.None); Assert.AreEqual(VMState.HALT, engine.Execute()); Assert.IsTrue(engine.ResultStack.Pop().GetBoolean()); - Assert.AreEqual(Neo.SmartContract.Helper.SignatureContractCost() * PolicyContract.DefaultExecFeeFactor, engine.GasConsumed); + Assert.AreEqual(Neo.SmartContract.Helper.SignatureContractCost() * PolicyContract.DefaultExecFeeFactor, engine.FeeConsumed); } [TestMethod] @@ -91,15 +145,15 @@ public void TestMultiSignatureContractCost() tx.Signers[0].Account = contract.ScriptHash; using ScriptBuilder invocationScript = new(); - invocationScript.EmitPush(Neo.Wallets.Helper.Sign(tx, _key, ProtocolSettings.Default.Network)); + invocationScript.EmitPush(Neo.Wallets.Helper.Sign(tx, _key, TestProtocolSettings.Default.Network)); - using var engine = ApplicationEngine.Create(TriggerType.Verification, tx, null, null, ProtocolSettings.Default); + using var engine = ApplicationEngine.Create(TriggerType.Verification, tx, null, null, TestProtocolSettings.Default); engine.LoadScript(contract.Script); engine.LoadScript(new Script(invocationScript.ToArray(), true), configureState: p => p.CallFlags = CallFlags.None); Assert.AreEqual(VMState.HALT, engine.Execute()); Assert.IsTrue(engine.ResultStack.Pop().GetBoolean()); - Assert.AreEqual(Neo.SmartContract.Helper.MultiSignatureContractCost(1, 1) * PolicyContract.DefaultExecFeeFactor, engine.GasConsumed); + Assert.AreEqual(Neo.SmartContract.Helper.MultiSignatureContractCost(1, 1) * PolicyContract.DefaultExecFeeFactor, engine.FeeConsumed); } } } diff --git a/tests/Neo.UnitTests/SmartContract/UT_InteropPrices.cs b/tests/Neo.UnitTests/SmartContract/UT_InteropPrices.cs index 96b20779e9..581bf42751 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_InteropPrices.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_InteropPrices.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_InteropPrices.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.SmartContract; @@ -63,9 +74,9 @@ public void ApplicationEngineRegularPut() debugger.StepInto(); debugger.StepInto(); debugger.StepInto(); - var setupPrice = ae.GasConsumed; + var setupPrice = ae.FeeConsumed; debugger.Execute(); - (ae.GasConsumed - setupPrice).Should().Be(ae.StoragePrice * value.Length + (1 << 15) * 30); + (ae.FeeConsumed - setupPrice).Should().Be(ae.StoragePrice * value.Length + (1 << 15) * 30); } /// @@ -94,9 +105,9 @@ public void ApplicationEngineReusedStorage_FullReuse() debugger.StepInto(); debugger.StepInto(); debugger.StepInto(); - var setupPrice = applicationEngine.GasConsumed; + var setupPrice = applicationEngine.FeeConsumed; debugger.Execute(); - (applicationEngine.GasConsumed - setupPrice).Should().Be(1 * applicationEngine.StoragePrice + (1 << 15) * 30); + (applicationEngine.FeeConsumed - setupPrice).Should().Be(1 * applicationEngine.StoragePrice + (1 << 15) * 30); } /// @@ -127,10 +138,10 @@ public void ApplicationEngineReusedStorage_PartialReuse() debugger.StepInto(); debugger.StepInto(); debugger.StepInto(); - var setupPrice = ae.GasConsumed; + var setupPrice = ae.FeeConsumed; debugger.StepInto(); debugger.StepInto(); - (ae.GasConsumed - setupPrice).Should().Be((1 + (oldValue.Length / 4) + value.Length - oldValue.Length) * ae.StoragePrice + (1 << 15) * 30); + (ae.FeeConsumed - setupPrice).Should().Be((1 + (oldValue.Length / 4) + value.Length - oldValue.Length) * ae.StoragePrice + (1 << 15) * 30); } /// @@ -165,9 +176,9 @@ public void ApplicationEngineReusedStorage_PartialReuseTwice() debugger.StepInto(); //push value debugger.StepInto(); //push key debugger.StepInto(); //syscall Storage.GetContext - var setupPrice = ae.GasConsumed; + var setupPrice = ae.FeeConsumed; debugger.StepInto(); //syscall Storage.Put - (ae.GasConsumed - setupPrice).Should().Be((sItem.Value.Length / 4 + 1) * ae.StoragePrice + (1 << 15) * 30); // = PUT basic fee + (ae.FeeConsumed - setupPrice).Should().Be((sItem.Value.Length / 4 + 1) * ae.StoragePrice + (1 << 15) * 30); // = PUT basic fee } private static byte[] CreateMultiplePutScript(byte[] key, byte[] value, int times = 2) diff --git a/tests/Neo.UnitTests/SmartContract/UT_InteropService.NEO.cs b/tests/Neo.UnitTests/SmartContract/UT_InteropService.NEO.cs index e6e0cb459b..4f0767bf2c 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_InteropService.NEO.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_InteropService.NEO.cs @@ -1,10 +1,19 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_InteropService.NEO.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; -using Neo.Cryptography.ECC; using Neo.IO; using Neo.Network.P2P; -using Neo.Network.P2P.Payloads; using Neo.SmartContract; using Neo.SmartContract.Manifest; using Neo.SmartContract.Native; @@ -22,13 +31,13 @@ public partial class UT_InteropService public void TestCheckSig() { var engine = GetEngine(true); - IVerifiable iv = engine.ScriptContainer; - byte[] message = iv.GetSignData(ProtocolSettings.Default.Network); + var iv = engine.ScriptContainer; + var message = iv.GetSignData(TestProtocolSettings.Default.Network); byte[] privateKey = { 0x01,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}; - KeyPair keyPair = new KeyPair(privateKey); - ECPoint pubkey = keyPair.PublicKey; - byte[] signature = Crypto.Sign(message, privateKey, pubkey.EncodePoint(false).Skip(1).ToArray()); + var keyPair = new KeyPair(privateKey); + var pubkey = keyPair.PublicKey; + var signature = Crypto.Sign(message, privateKey); engine.CheckSig(pubkey.EncodePoint(false), signature).Should().BeTrue(); Action action = () => engine.CheckSig(new byte[70], signature); action.Should().Throw(); @@ -38,20 +47,20 @@ public void TestCheckSig() public void TestCrypto_CheckMultiSig() { var engine = GetEngine(true); - IVerifiable iv = engine.ScriptContainer; - byte[] message = iv.GetSignData(ProtocolSettings.Default.Network); + var iv = engine.ScriptContainer; + var message = iv.GetSignData(TestProtocolSettings.Default.Network); byte[] privkey1 = { 0x01,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}; - KeyPair key1 = new KeyPair(privkey1); - ECPoint pubkey1 = key1.PublicKey; - byte[] signature1 = Crypto.Sign(message, privkey1, pubkey1.EncodePoint(false).Skip(1).ToArray()); + var key1 = new KeyPair(privkey1); + var pubkey1 = key1.PublicKey; + var signature1 = Crypto.Sign(message, privkey1); byte[] privkey2 = { 0x01,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02}; - KeyPair key2 = new KeyPair(privkey2); - ECPoint pubkey2 = key2.PublicKey; - byte[] signature2 = Crypto.Sign(message, privkey2, pubkey2.EncodePoint(false).Skip(1).ToArray()); + var key2 = new KeyPair(privkey2); + var pubkey2 = key2.PublicKey; + var signature2 = Crypto.Sign(message, privkey2); var pubkeys = new[] { @@ -121,12 +130,14 @@ public void TestContract_Create() var script_exceedMaxLength = new NefFile() { - Script = new byte[NefFile.MaxScriptLength - 1], + Script = new byte[ExecutionEngineLimits.Default.MaxItemSize - 50], Source = string.Empty, Compiler = "", - Tokens = System.Array.Empty() + Tokens = Array.Empty() }; - script_exceedMaxLength.CheckSum = NefFile.ComputeChecksum(nef); + script_exceedMaxLength.CheckSum = NefFile.ComputeChecksum(script_exceedMaxLength); + + Assert.ThrowsException(() => script_exceedMaxLength.ToArray().AsSerializable()); Assert.ThrowsException(() => snapshot.DeployContract(UInt160.Zero, script_exceedMaxLength.ToArray(), manifest.ToJson().ToByteArray(true))); var script_zeroLength = System.Array.Empty(); @@ -155,7 +166,7 @@ public void TestContract_Update() Script = new[] { (byte)OpCode.RET }, Source = string.Empty, Compiler = "", - Tokens = System.Array.Empty() + Tokens = Array.Empty() }; nef.CheckSum = NefFile.ComputeChecksum(nef); Assert.ThrowsException(() => snapshot.UpdateContract(null, nef.ToArray(), new byte[0])); @@ -163,13 +174,13 @@ public void TestContract_Update() var manifest = TestUtils.CreateDefaultManifest(); byte[] privkey = { 0x01,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}; - KeyPair key = new KeyPair(privkey); - ECPoint pubkey = key.PublicKey; + var key = new KeyPair(privkey); + var pubkey = key.PublicKey; var state = TestUtils.GetContract(); - byte[] signature = Crypto.Sign(state.Hash.ToArray(), privkey, pubkey.EncodePoint(false).Skip(1).ToArray()); + var signature = Crypto.Sign(state.Hash.ToArray(), privkey); manifest.Groups = new ContractGroup[] { - new ContractGroup() + new() { PubKey = pubkey, Signature = signature diff --git a/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs b/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs index 081fb87661..51f26d22eb 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_InteropService.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Akka.TestKit.Xunit2; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -45,7 +56,7 @@ public void Runtime_GetNotifications_Test() scriptHash2 = script.ToArray().ToScriptHash(); snapshot.DeleteContract(scriptHash2); - ContractState contract = TestUtils.GetContract(script.ToArray(), TestUtils.CreateManifest("test", ContractParameterType.Any, ContractParameterType.Integer, ContractParameterType.Integer)); + var contract = TestUtils.GetContract(script.ToArray(), TestUtils.CreateManifest("test", ContractParameterType.Any, ContractParameterType.Integer, ContractParameterType.Integer)); contract.Manifest.Abi.Events = new[] { new ContractEventDescriptor @@ -60,12 +71,20 @@ public void Runtime_GetNotifications_Test() } } }; + contract.Manifest.Permissions = new ContractPermission[] + { + new ContractPermission + { + Contract = ContractPermissionDescriptor.Create(scriptHash2), + Methods = WildcardContainer.Create(new string[]{"test"}) + } + }; snapshot.AddContract(scriptHash2, contract); } // Wrong length - using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot)) + using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, null, ProtocolSettings.Default)) using (var script = new ScriptBuilder()) { // Retrive @@ -82,7 +101,7 @@ public void Runtime_GetNotifications_Test() // All test - using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot)) + using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, null, ProtocolSettings.Default)) using (var script = new ScriptBuilder()) { // Notification @@ -122,6 +141,14 @@ public void Runtime_GetNotifications_Test() Parameters = System.Array.Empty() } } + }, + Permissions = new ContractPermission[] + { + new ContractPermission + { + Contract = ContractPermissionDescriptor.Create(scriptHash2), + Methods = WildcardContainer.Create(new string[]{"test"}) + } } } }; @@ -151,7 +178,7 @@ public void Runtime_GetNotifications_Test() // Script notifications - using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot)) + using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, null, ProtocolSettings.Default)) using (var script = new ScriptBuilder()) { // Notification @@ -191,6 +218,14 @@ public void Runtime_GetNotifications_Test() Parameters = System.Array.Empty() } } + }, + Permissions = new ContractPermission[] + { + new ContractPermission + { + Contract = ContractPermissionDescriptor.Create(scriptHash2), + Methods = WildcardContainer.Create(new string[]{"test"}) + } } } }; @@ -283,8 +318,8 @@ public void TestRuntime_CheckWitness() { byte[] privateKey = { 0x01,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}; - KeyPair keyPair = new(privateKey); - ECPoint pubkey = keyPair.PublicKey; + var keyPair = new KeyPair(privateKey); + var pubkey = keyPair.PublicKey; var engine = GetEngine(true); ((Transaction)engine.ScriptContainer).Signers[0].Account = Contract.CreateSignatureRedeemScript(pubkey).ToScriptHash(); @@ -300,11 +335,24 @@ public void TestRuntime_CheckWitness() action.Should().Throw(); } + [TestMethod] + public void TestRuntime_CheckWitness_Null_ScriptContainer() + { + byte[] privateKey = { 0x01,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}; + var keyPair = new KeyPair(privateKey); + var pubkey = keyPair.PublicKey; + + var engine = GetEngine(); + + engine.CheckWitness(pubkey.EncodePoint(true)).Should().BeFalse(); + } + [TestMethod] public void TestRuntime_Log() { var engine = GetEngine(true); - string message = "hello"; + var message = "hello"; ApplicationEngine.Log += LogEvent; engine.RuntimeLog(Encoding.UTF8.GetBytes(message)); ((Transaction)engine.ScriptContainer).Script.Span.ToHexString().Should().Be(new byte[] { 0x01, 0x02, 0x03 }.ToHexString()); @@ -326,20 +374,62 @@ public void TestRuntime_GetInvocationCounter() Assert.AreEqual(1, engine.GetInvocationCounter()); } + [TestMethod] + public void TestRuntime_GetCurrentSigners() + { + using var engine = GetEngine(hasContainer: true); + Assert.AreEqual(UInt160.Zero, engine.GetCurrentSigners()[0].Account); + } + + [TestMethod] + public void TestRuntime_GetCurrentSigners_SysCall() + { + using ScriptBuilder script = new(); + script.EmitSysCall(ApplicationEngine.System_Runtime_CurrentSigners.Hash); + + // Null + + using var engineA = GetEngine(hasSnapshot: true, addScript: false, hasContainer: false); + + engineA.LoadScript(script.ToArray()); + engineA.Execute(); + Assert.AreEqual(engineA.State, VMState.HALT); + + var result = engineA.ResultStack.Pop(); + result.Should().BeOfType(typeof(VM.Types.Null)); + + // Not null + + using var engineB = GetEngine(hasSnapshot: true, addScript: false, hasContainer: true); + + engineB.LoadScript(script.ToArray()); + engineB.Execute(); + Assert.AreEqual(engineB.State, VMState.HALT); + + result = engineB.ResultStack.Pop(); + result.Should().BeOfType(typeof(VM.Types.Array)); + (result as VM.Types.Array).Count.Should().Be(1); + result = (result as VM.Types.Array)[0]; + result.Should().BeOfType(typeof(VM.Types.Array)); + (result as VM.Types.Array).Count.Should().Be(5); + result = (result as VM.Types.Array)[0]; // Address + Assert.AreEqual(UInt160.Zero, new UInt160(result.GetSpan())); + } + [TestMethod] public void TestCrypto_Verify() { var engine = GetEngine(true); - IVerifiable iv = engine.ScriptContainer; - byte[] message = iv.GetSignData(ProtocolSettings.Default.Network); + var iv = engine.ScriptContainer; + var message = iv.GetSignData(TestProtocolSettings.Default.Network); byte[] privateKey = { 0x01,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}; KeyPair keyPair = new(privateKey); - ECPoint pubkey = keyPair.PublicKey; - byte[] signature = Crypto.Sign(message, privateKey, pubkey.EncodePoint(false).Skip(1).ToArray()); + var pubkey = keyPair.PublicKey; + var signature = Crypto.Sign(message, privateKey); engine.CheckSig(pubkey.EncodePoint(false), signature).Should().BeTrue(); - byte[] wrongkey = pubkey.EncodePoint(false); + var wrongkey = pubkey.EncodePoint(false); wrongkey[0] = 5; Assert.ThrowsException(() => engine.CheckSig(wrongkey, signature)); } @@ -358,7 +448,7 @@ public void TestBlockchain_GetBlock() NativeContract.Ledger.GetBlock(engine.Snapshot, UInt256.Zero).Should().BeNull(); - byte[] data1 = new byte[] { 0x01, 0x01, 0x01 ,0x01, 0x01, 0x01, 0x01, 0x01, + var data1 = new byte[] { 0x01, 0x01, 0x01 ,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}; @@ -370,7 +460,7 @@ public void TestBlockchain_GetBlock() public void TestBlockchain_GetTransaction() { var engine = GetEngine(true, true); - byte[] data1 = new byte[] { 0x01, 0x01, 0x01 ,0x01, 0x01, 0x01, 0x01, 0x01, + var data1 = new byte[] { 0x01, 0x01, 0x01 ,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}; @@ -403,7 +493,7 @@ public void TestBlockchain_GetTransactionHeight() public void TestBlockchain_GetContract() { var engine = GetEngine(true, true); - byte[] data1 = new byte[] { 0x01, 0x01, 0x01 ,0x01, 0x01, + var data1 = new byte[] { 0x01, 0x01, 0x01 ,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }; @@ -572,11 +662,11 @@ public void TestStorageContext_AsReadOnly() public void TestContract_Call() { var snapshot = TestBlockchain.GetTestSnapshot(); - string method = "method"; + var method = "method"; var args = new VM.Types.Array { 0, 1 }; var state = TestUtils.GetContract(method, args.Count); - var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot); + var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, null, ProtocolSettings.Default); engine.LoadScript(new byte[] { 0x01 }); engine.Snapshot.AddContract(state.Hash, state); @@ -630,13 +720,13 @@ public void TestContract_Destroy() [TestMethod] public void TestContract_CreateStandardAccount() { - ECPoint pubkey = ECPoint.Parse("024b817ef37f2fc3d4a33fe36687e592d9f30fe24b3e28187dc8f12b3b3b2b839e", ECCurve.Secp256r1); + var pubkey = ECPoint.Parse("024b817ef37f2fc3d4a33fe36687e592d9f30fe24b3e28187dc8f12b3b3b2b839e", ECCurve.Secp256r1); GetEngine().CreateStandardAccount(pubkey).ToArray().ToHexString().Should().Be("c44ea575c5f79638f0e73f39d7bd4b3337c81691"); } public static void LogEvent(object sender, LogEventArgs args) { - Transaction tx = (Transaction)args.ScriptContainer; + var tx = (Transaction)args.ScriptContainer; tx.Script = new byte[] { 0x01, 0x02, 0x03 }; } @@ -645,7 +735,7 @@ private static ApplicationEngine GetEngine(bool hasContainer = false, bool hasSn var tx = hasContainer ? TestUtils.GetTransaction(UInt160.Zero) : null; var snapshot = hasSnapshot ? TestBlockchain.GetTestSnapshot() : null; var block = hasBlock ? new Block { Header = new Header() } : null; - ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, tx, snapshot, block, TestBlockchain.TheNeoSystem.Settings, gas: gas); + var engine = ApplicationEngine.Create(TriggerType.Application, tx, snapshot, block, TestBlockchain.TheNeoSystem.Settings, gas: gas); if (addScript) engine.LoadScript(new byte[] { 0x01 }); return engine; } diff --git a/tests/Neo.UnitTests/SmartContract/UT_JsonSerializer.cs b/tests/Neo.UnitTests/SmartContract/UT_JsonSerializer.cs index bf608bc57e..fc29c95c12 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_JsonSerializer.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_JsonSerializer.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_JsonSerializer.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Json; using Neo.SmartContract; @@ -261,7 +272,7 @@ public void Serialize_Map_Test() [TestMethod] public void Deserialize_Map_Test() { - ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, null); + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, null, null, ProtocolSettings.Default); var items = JsonSerializer.Deserialize(engine, JObject.Parse("{\"test1\":123,\"test2\":321}"), ExecutionEngineLimits.Default); Assert.IsInstanceOfType(items, typeof(Map)); @@ -291,7 +302,7 @@ public void Serialize_Array_Bool_Str_Num() [TestMethod] public void Deserialize_Array_Bool_Str_Num() { - ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, null); + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, null, null, ProtocolSettings.Default); var items = JsonSerializer.Deserialize(engine, JObject.Parse("[true,\"test\",123,9.05E+28]"), ExecutionEngineLimits.Default); Assert.IsInstanceOfType(items, typeof(VM.Types.Array)); @@ -322,7 +333,7 @@ public void Serialize_Array_OfArray() [TestMethod] public void Deserialize_Array_OfArray() { - ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, null); + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, null, null, ProtocolSettings.Default); var items = JsonSerializer.Deserialize(engine, JObject.Parse("[[true,\"test1\",123],[true,\"test2\",321]]"), ExecutionEngineLimits.Default); Assert.IsInstanceOfType(items, typeof(VM.Types.Array)); diff --git a/tests/Neo.UnitTests/SmartContract/UT_KeyBuilder.cs b/tests/Neo.UnitTests/SmartContract/UT_KeyBuilder.cs index d03a411b4d..3ff52c1324 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_KeyBuilder.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_KeyBuilder.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_KeyBuilder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.SmartContract; @@ -6,11 +17,6 @@ namespace Neo.UnitTests.SmartContract [TestClass] public class UT_KeyBuilder { - private struct TestKey - { - public int Value; - } - [TestMethod] public void Test() { @@ -28,11 +34,11 @@ public void Test() Assert.AreEqual("010000000203040000000000000000000000000000000000000000", key.ToArray().ToHexString()); key = new KeyBuilder(1, 2); - key = key.Add(new TestKey { Value = 123 }); - Assert.AreEqual("01000000027b000000", key.ToArray().ToHexString()); + key = key.AddBigEndian(123); + Assert.AreEqual("01000000020000007b", key.ToArray().ToHexString()); key = new KeyBuilder(1, 0); - key = key.AddBigEndian(new TestKey { Value = 1 }); + key = key.AddBigEndian(1); Assert.AreEqual("010000000000000001", key.ToArray().ToHexString()); } } diff --git a/tests/Neo.UnitTests/SmartContract/UT_LogEventArgs.cs b/tests/Neo.UnitTests/SmartContract/UT_LogEventArgs.cs index 8dc3699c82..4e587a2246 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_LogEventArgs.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_LogEventArgs.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_LogEventArgs.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Network.P2P.Payloads; using Neo.SmartContract; diff --git a/tests/Neo.UnitTests/SmartContract/UT_MethodToken.cs b/tests/Neo.UnitTests/SmartContract/UT_MethodToken.cs index 8d530eefd9..9d687e1053 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_MethodToken.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_MethodToken.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_MethodToken.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; using Neo.SmartContract; diff --git a/tests/Neo.UnitTests/SmartContract/UT_NefFile.cs b/tests/Neo.UnitTests/SmartContract/UT_NefFile.cs index 24eff0cba2..40142e93be 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_NefFile.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_NefFile.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_NefFile.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; diff --git a/tests/Neo.UnitTests/SmartContract/UT_NotifyEventArgs.cs b/tests/Neo.UnitTests/SmartContract/UT_NotifyEventArgs.cs index e83f8fd7fc..59afbbb760 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_NotifyEventArgs.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_NotifyEventArgs.cs @@ -1,7 +1,20 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_NotifyEventArgs.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Network.P2P.Payloads; using Neo.SmartContract; +using Neo.VM; +using Neo.VM.Types; namespace Neo.UnitTests.SmartContract { @@ -16,5 +29,37 @@ public void TestGetScriptContainer() NotifyEventArgs args = new NotifyEventArgs(container, script_hash, "Test", null); args.ScriptContainer.Should().Be(container); } + + + [TestMethod] + public void TestIssue3300() // https://github.com/neo-project/neo/issues/3300 + { + using var engine = ApplicationEngine.Create(TriggerType.Application, null, null, settings: TestProtocolSettings.Default, gas: 1100_00000000); + using (var script = new ScriptBuilder()) + { + // Build call script calling disallowed method. + script.Emit(OpCode.NOP); + // Mock executing state to be a contract-based. + engine.LoadScript(script.ToArray()); + } + + var ns = new Array(engine.ReferenceCounter); + for (var i = 0; i < 500; i++) + { + ns.Add(""); + }; + + var hash = UInt160.Parse("0x179ab5d297fd34ecd48643894242fc3527f42853"); + engine.SendNotification(hash, "Test", ns); + // This should have being 0, but we have optimized the vm to not clean the reference counter + // unless it is necessary, so the reference counter will be 1000. + // Same reason why its 1504 instead of 504. + Assert.AreEqual(1000, engine.ReferenceCounter.Count); + // This will make a deepcopy for the notification, along with the 500 state items. + engine.GetNotifications(hash); + // With the fix of issue 3300, the reference counter calculates not only + // the notifaction items, but also the subitems of the notification state. + Assert.AreEqual(1504, engine.ReferenceCounter.Count); + } } } diff --git a/tests/Neo.UnitTests/SmartContract/UT_OpCodePrices.cs b/tests/Neo.UnitTests/SmartContract/UT_OpCodePrices.cs index fa82f194d2..c373930c6e 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_OpCodePrices.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_OpCodePrices.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_OpCodePrices.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.SmartContract; using Neo.VM; @@ -12,7 +23,20 @@ public class UT_OpCodePrices public void AllOpcodePriceAreSet() { foreach (OpCode opcode in Enum.GetValues(typeof(OpCode))) - Assert.IsTrue(ApplicationEngine.OpCodePrices.ContainsKey(opcode), opcode.ToString()); + { +#pragma warning disable CS0618 // Type or member is obsolete + Assert.IsTrue(ApplicationEngine.OpCodePrices.ContainsKey(opcode), opcode.ToString(), $"{opcode} without price"); + Assert.AreEqual(ApplicationEngine.OpCodePrices[opcode], ApplicationEngine.OpCodePriceTable[(byte)opcode], $"{opcode} price mismatch"); +#pragma warning restore CS0618 // Type or member is obsolete + + if (opcode == OpCode.RET || + opcode == OpCode.SYSCALL || + opcode == OpCode.ABORT || + opcode == OpCode.ABORTMSG) + continue; + + Assert.AreNotEqual(0, ApplicationEngine.OpCodePriceTable[(byte)opcode], $"{opcode} without price"); + } } } } diff --git a/tests/Neo.UnitTests/SmartContract/UT_SmartContractHelper.cs b/tests/Neo.UnitTests/SmartContract/UT_SmartContractHelper.cs index 773c3ed4e1..c14b15c241 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_SmartContractHelper.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_SmartContractHelper.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_SmartContractHelper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; using Neo.Network.P2P.Payloads; @@ -131,7 +142,7 @@ public void TestVerifyWitnesses() Hashes = new UInt256[1] { UInt256.Zero }, }); BlocksDelete(snapshot1, index1); - Assert.AreEqual(false, Neo.SmartContract.Helper.VerifyWitnesses(new Header() { PrevHash = index1 }, ProtocolSettings.Default, snapshot1, 100)); + Assert.AreEqual(false, Neo.SmartContract.Helper.VerifyWitnesses(new Header() { PrevHash = index1 }, TestProtocolSettings.Default, snapshot1, 100)); var snapshot2 = TestBlockchain.GetTestSnapshot(); UInt256 index2 = UInt256.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff01"); @@ -152,7 +163,7 @@ public void TestVerifyWitnesses() snapshot2.AddContract(UInt160.Zero, new ContractState()); snapshot2.DeleteContract(UInt160.Zero); - Assert.AreEqual(false, Neo.SmartContract.Helper.VerifyWitnesses(header2, ProtocolSettings.Default, snapshot2, 100)); + Assert.AreEqual(false, Neo.SmartContract.Helper.VerifyWitnesses(header2, TestProtocolSettings.Default, snapshot2, 100)); var snapshot3 = TestBlockchain.GetTestSnapshot(); UInt256 index3 = UInt256.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff01"); @@ -184,7 +195,7 @@ public void TestVerifyWitnesses() Hash = Array.Empty().ToScriptHash(), Manifest = TestUtils.CreateManifest("verify", ContractParameterType.Boolean, ContractParameterType.Signature), }); - Assert.AreEqual(false, Neo.SmartContract.Helper.VerifyWitnesses(header3, ProtocolSettings.Default, snapshot3, 100)); + Assert.AreEqual(false, Neo.SmartContract.Helper.VerifyWitnesses(header3, TestProtocolSettings.Default, snapshot3, 100)); // Smart contract verification @@ -200,7 +211,7 @@ public void TestVerifyWitnesses() Witnesses = new Witness[] { new Witness() { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() } } }; - Assert.AreEqual(true, Neo.SmartContract.Helper.VerifyWitnesses(tx, ProtocolSettings.Default, snapshot3, 1000)); + Assert.AreEqual(true, Neo.SmartContract.Helper.VerifyWitnesses(tx, TestProtocolSettings.Default, snapshot3, 1000)); } private static void BlocksDelete(DataCache snapshot, UInt256 hash) diff --git a/tests/Neo.UnitTests/SmartContract/UT_Syscalls.cs b/tests/Neo.UnitTests/SmartContract/UT_Syscalls.cs index c166efdb22..7791b8acef 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_Syscalls.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_Syscalls.cs @@ -1,5 +1,17 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Syscalls.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Akka.TestKit.Xunit2; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography.ECC; using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.SmartContract; @@ -70,7 +82,7 @@ public void System_Blockchain_GetBlock() const byte Prefix_CurrentBlock = 12; var height = snapshot[NativeContract.Ledger.CreateStorageKey(Prefix_CurrentBlock)].GetInteroperable(); - height.Index = block.Index + ProtocolSettings.Default.MaxTraceableBlocks; + height.Index = block.Index + TestProtocolSettings.Default.MaxTraceableBlocks; UT_SmartContractHelper.BlocksAdd(snapshot, block.Hash, block); snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_Transaction, tx.Hash), new StorageItem(new TransactionState @@ -121,7 +133,16 @@ public void System_ExecutionEngine_GetScriptContainer() var tx = new Transaction() { Script = new byte[] { 0x01 }, - Signers = new Signer[] { new Signer() { Account = UInt160.Zero, Scopes = WitnessScope.None } }, + Signers = new Signer[] { + new Signer() + { + Account = UInt160.Zero, + Scopes = WitnessScope.None, + AllowedContracts = Array.Empty(), + AllowedGroups = Array.Empty(), + Rules = Array.Empty(), + } + }, Attributes = Array.Empty(), NetworkFee = 0x02, SystemFee = 0x03, @@ -236,7 +257,7 @@ public void System_Runtime_GetInvocationCounter() // Execute - var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot); + var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, null, ProtocolSettings.Default); engine.LoadScript(script.ToArray()); Assert.AreEqual(VMState.HALT, engine.Execute()); diff --git a/tests/Neo.UnitTests/TestBlockchain.cs b/tests/Neo.UnitTests/TestBlockchain.cs index f1ea5eb975..f7e06d0595 100644 --- a/tests/Neo.UnitTests/TestBlockchain.cs +++ b/tests/Neo.UnitTests/TestBlockchain.cs @@ -1,3 +1,16 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TestBlockchain.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Akka.Actor; +using Neo.Ledger; using Neo.Persistence; using System; @@ -7,11 +20,25 @@ public static class TestBlockchain { public static readonly NeoSystem TheNeoSystem; public static readonly UInt160[] DefaultExtensibleWitnessWhiteList; + private static readonly MemoryStore Store = new(); + + private class StoreProvider : IStoreProvider + { + public string Name => "TestProvider"; + + public IStore GetStore(string path) => Store; + } static TestBlockchain() { Console.WriteLine("initialize NeoSystem"); - TheNeoSystem = new NeoSystem(ProtocolSettings.Default, null, null); + TheNeoSystem = new NeoSystem(TestProtocolSettings.Default, new StoreProvider()); + } + + internal static void ResetStore() + { + Store.Reset(); + TheNeoSystem.Blockchain.Ask(new Blockchain.Initialize()).Wait(); } internal static DataCache GetTestSnapshot() diff --git a/tests/Neo.UnitTests/TestProtocolSettings.cs b/tests/Neo.UnitTests/TestProtocolSettings.cs new file mode 100644 index 0000000000..b12f5c9a85 --- /dev/null +++ b/tests/Neo.UnitTests/TestProtocolSettings.cs @@ -0,0 +1,65 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TestProtocolSettings.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Cryptography.ECC; + +namespace Neo.UnitTests +{ + public static class TestProtocolSettings + { + public static ProtocolSettings Default = new() + { + Network = 0x334F454Eu, + AddressVersion = ProtocolSettings.Default.AddressVersion, + StandbyCommittee = new[] + { + //Validators + ECPoint.Parse("03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", ECCurve.Secp256r1), + ECPoint.Parse("02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093", ECCurve.Secp256r1), + ECPoint.Parse("03b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a", ECCurve.Secp256r1), + ECPoint.Parse("02ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba554", ECCurve.Secp256r1), + ECPoint.Parse("024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d", ECCurve.Secp256r1), + ECPoint.Parse("02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e", ECCurve.Secp256r1), + ECPoint.Parse("02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70", ECCurve.Secp256r1), + //Other Members + ECPoint.Parse("023a36c72844610b4d34d1968662424011bf783ca9d984efa19a20babf5582f3fe", ECCurve.Secp256r1), + ECPoint.Parse("03708b860c1de5d87f5b151a12c2a99feebd2e8b315ee8e7cf8aa19692a9e18379", ECCurve.Secp256r1), + ECPoint.Parse("03c6aa6e12638b36e88adc1ccdceac4db9929575c3e03576c617c49cce7114a050", ECCurve.Secp256r1), + ECPoint.Parse("03204223f8c86b8cd5c89ef12e4f0dbb314172e9241e30c9ef2293790793537cf0", ECCurve.Secp256r1), + ECPoint.Parse("02a62c915cf19c7f19a50ec217e79fac2439bbaad658493de0c7d8ffa92ab0aa62", ECCurve.Secp256r1), + ECPoint.Parse("03409f31f0d66bdc2f70a9730b66fe186658f84a8018204db01c106edc36553cd0", ECCurve.Secp256r1), + ECPoint.Parse("0288342b141c30dc8ffcde0204929bb46aed5756b41ef4a56778d15ada8f0c6654", ECCurve.Secp256r1), + ECPoint.Parse("020f2887f41474cfeb11fd262e982051c1541418137c02a0f4961af911045de639", ECCurve.Secp256r1), + ECPoint.Parse("0222038884bbd1d8ff109ed3bdef3542e768eef76c1247aea8bc8171f532928c30", ECCurve.Secp256r1), + ECPoint.Parse("03d281b42002647f0113f36c7b8efb30db66078dfaaa9ab3ff76d043a98d512fde", ECCurve.Secp256r1), + ECPoint.Parse("02504acbc1f4b3bdad1d86d6e1a08603771db135a73e61c9d565ae06a1938cd2ad", ECCurve.Secp256r1), + ECPoint.Parse("0226933336f1b75baa42d42b71d9091508b638046d19abd67f4e119bf64a7cfb4d", ECCurve.Secp256r1), + ECPoint.Parse("03cdcea66032b82f5c30450e381e5295cae85c5e6943af716cc6b646352a6067dc", ECCurve.Secp256r1), + ECPoint.Parse("02cd5a5547119e24feaa7c2a0f37b8c9366216bab7054de0065c9be42084003c8a", ECCurve.Secp256r1) + }, + ValidatorsCount = 7, + SeedList = new[] + { + "seed1.neo.org:10333", + "seed2.neo.org:10333", + "seed3.neo.org:10333", + "seed4.neo.org:10333", + "seed5.neo.org:10333" + }, + MillisecondsPerBlock = ProtocolSettings.Default.MillisecondsPerBlock, + MaxTransactionsPerBlock = ProtocolSettings.Default.MaxTransactionsPerBlock, + MemoryPoolMaxTransactions = ProtocolSettings.Default.MemoryPoolMaxTransactions, + MaxTraceableBlocks = ProtocolSettings.Default.MaxTraceableBlocks, + InitialGasDistribution = ProtocolSettings.Default.InitialGasDistribution, + Hardforks = ProtocolSettings.Default.Hardforks + }; + } +} diff --git a/tests/Neo.UnitTests/TestUtils.cs b/tests/Neo.UnitTests/TestUtils.cs index e3cb45d16c..f8f63de53a 100644 --- a/tests/Neo.UnitTests/TestUtils.cs +++ b/tests/Neo.UnitTests/TestUtils.cs @@ -1,5 +1,17 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TestUtils.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Neo.Cryptography; +using Neo.Cryptography.ECC; using Neo.IO; using Neo.Json; using Neo.Network.P2P.Payloads; @@ -96,7 +108,7 @@ public static NEP6Wallet GenerateTestWallet(string password) wallet["accounts"] = new JArray(); wallet["extra"] = null; wallet.ToString().Should().Be("{\"name\":\"noname\",\"version\":\"1.0\",\"scrypt\":{\"n\":2,\"r\":1,\"p\":1},\"accounts\":[],\"extra\":null}"); - return new NEP6Wallet(null, password, ProtocolSettings.Default, wallet); + return new NEP6Wallet(null, password, TestProtocolSettings.Default, wallet); } public static Transaction GetTransaction(UInt160 sender) @@ -108,7 +120,10 @@ public static Transaction GetTransaction(UInt160 sender) Signers = new[]{ new Signer() { Account = sender, - Scopes = WitnessScope.CalledByEntry + Scopes = WitnessScope.CalledByEntry, + AllowedContracts = Array.Empty(), + AllowedGroups = Array.Empty(), + Rules = Array.Empty(), } }, Witnesses = new Witness[]{ new Witness { diff --git a/tests/Neo.UnitTests/TestVerifiable.cs b/tests/Neo.UnitTests/TestVerifiable.cs index e21178f84f..12dc7992f6 100644 --- a/tests/Neo.UnitTests/TestVerifiable.cs +++ b/tests/Neo.UnitTests/TestVerifiable.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TestVerifiable.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.Persistence; @@ -40,7 +51,7 @@ public void Serialize(BinaryWriter writer) public void SerializeUnsigned(BinaryWriter writer) { - writer.Write((string)testStr); + writer.Write(testStr); } } } diff --git a/tests/Neo.UnitTests/TestWalletAccount.cs b/tests/Neo.UnitTests/TestWalletAccount.cs index bc6d04825b..08686cf70b 100644 --- a/tests/Neo.UnitTests/TestWalletAccount.cs +++ b/tests/Neo.UnitTests/TestWalletAccount.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TestWalletAccount.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Moq; using Neo.SmartContract; using Neo.Wallets; @@ -13,7 +24,7 @@ class TestWalletAccount : WalletAccount public override KeyPair GetKey() => key; public TestWalletAccount(UInt160 hash) - : base(hash, ProtocolSettings.Default) + : base(hash, TestProtocolSettings.Default) { var mock = new Mock(); mock.SetupGet(p => p.ScriptHash).Returns(hash); diff --git a/tests/Neo.UnitTests/UT_BigDecimal.cs b/tests/Neo.UnitTests/UT_BigDecimal.cs index 9dc21f5b34..9e3c50a9db 100644 --- a/tests/Neo.UnitTests/UT_BigDecimal.cs +++ b/tests/Neo.UnitTests/UT_BigDecimal.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_BigDecimal.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; diff --git a/tests/Neo.UnitTests/UT_DataCache.cs b/tests/Neo.UnitTests/UT_DataCache.cs index ee6e05249d..008f166b6f 100644 --- a/tests/Neo.UnitTests/UT_DataCache.cs +++ b/tests/Neo.UnitTests/UT_DataCache.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_DataCache.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Persistence; using Neo.SmartContract; diff --git a/tests/Neo.UnitTests/UT_Helper.cs b/tests/Neo.UnitTests/UT_Helper.cs index fda1e40ace..b7dc4f1681 100644 --- a/tests/Neo.UnitTests/UT_Helper.cs +++ b/tests/Neo.UnitTests/UT_Helper.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Helper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO.Caching; @@ -19,7 +30,7 @@ public class UT_Helper public void GetSignData() { TestVerifiable verifiable = new(); - byte[] res = verifiable.GetSignData(ProtocolSettings.Default.Network); + byte[] res = verifiable.GetSignData(TestProtocolSettings.Default.Network); res.ToHexString().Should().Be("4e454f3350b51da6bb366be3ea50140cda45ba7df575287c0371000b2037ed3898ff8bf5"); } @@ -27,7 +38,7 @@ public void GetSignData() public void Sign() { TestVerifiable verifiable = new(); - byte[] res = verifiable.Sign(new KeyPair(TestUtils.GetByteArray(32, 0x42)), ProtocolSettings.Default.Network); + byte[] res = verifiable.Sign(new KeyPair(TestUtils.GetByteArray(32, 0x42)), TestProtocolSettings.Default.Network); res.Length.Should().Be(64); } diff --git a/tests/Neo.UnitTests/UT_NeoSystem.cs b/tests/Neo.UnitTests/UT_NeoSystem.cs index 646d214f6c..a88498b504 100644 --- a/tests/Neo.UnitTests/UT_NeoSystem.cs +++ b/tests/Neo.UnitTests/UT_NeoSystem.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_NeoSystem.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/tests/Neo.UnitTests/UT_ProtocolSettings.cs b/tests/Neo.UnitTests/UT_ProtocolSettings.cs index 0af209e10f..e5d829ef8b 100644 --- a/tests/Neo.UnitTests/UT_ProtocolSettings.cs +++ b/tests/Neo.UnitTests/UT_ProtocolSettings.cs @@ -1,7 +1,20 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ProtocolSettings.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.SmartContract.Native; +using Neo.Cryptography.ECC; using Neo.Wallets; +using System; +using System.IO; namespace Neo.UnitTests { @@ -12,40 +25,314 @@ public class UT_ProtocolSettings public void CheckFirstLetterOfAddresses() { UInt160 min = UInt160.Parse("0x0000000000000000000000000000000000000000"); - min.ToAddress(ProtocolSettings.Default.AddressVersion)[0].Should().Be('N'); + min.ToAddress(TestProtocolSettings.Default.AddressVersion)[0].Should().Be('N'); UInt160 max = UInt160.Parse("0xffffffffffffffffffffffffffffffffffffffff"); - max.ToAddress(ProtocolSettings.Default.AddressVersion)[0].Should().Be('N'); + max.ToAddress(TestProtocolSettings.Default.AddressVersion)[0].Should().Be('N'); } [TestMethod] public void Default_Network_should_be_mainnet_Network_value() { var mainNetNetwork = 0x334F454Eu; - ProtocolSettings.Default.Network.Should().Be(mainNetNetwork); + TestProtocolSettings.Default.Network.Should().Be(mainNetNetwork); } [TestMethod] public void TestGetMemoryPoolMaxTransactions() { - ProtocolSettings.Default.MemoryPoolMaxTransactions.Should().Be(50000); + TestProtocolSettings.Default.MemoryPoolMaxTransactions.Should().Be(50000); } [TestMethod] public void TestGetMillisecondsPerBlock() { - ProtocolSettings.Default.MillisecondsPerBlock.Should().Be(15000); + TestProtocolSettings.Default.MillisecondsPerBlock.Should().Be(15000); + } + + [TestMethod] + public void HardForkTestBAndNotA() + { + string json = CreateHKSettings("\"HF_Basilisk\": 4120000"); + + var file = Path.GetTempFileName(); + File.WriteAllText(file, json); + ProtocolSettings settings = ProtocolSettings.Load(file, false); + File.Delete(file); + + settings.Hardforks[Hardfork.HF_Aspidochelone].Should().Be(0); + settings.Hardforks[Hardfork.HF_Basilisk].Should().Be(4120000); + + // Check IsHardforkEnabled + + settings.IsHardforkEnabled(Hardfork.HF_Aspidochelone, 0).Should().BeTrue(); + settings.IsHardforkEnabled(Hardfork.HF_Aspidochelone, 10).Should().BeTrue(); + settings.IsHardforkEnabled(Hardfork.HF_Basilisk, 0).Should().BeFalse(); + settings.IsHardforkEnabled(Hardfork.HF_Basilisk, 10).Should().BeFalse(); + settings.IsHardforkEnabled(Hardfork.HF_Basilisk, 4120000).Should().BeTrue(); + } + + [TestMethod] + public void HardForkTestAAndNotB() + { + string json = CreateHKSettings("\"HF_Aspidochelone\": 0"); + + var file = Path.GetTempFileName(); + File.WriteAllText(file, json); + ProtocolSettings settings = ProtocolSettings.Load(file, false); + File.Delete(file); + + settings.Hardforks[Hardfork.HF_Aspidochelone].Should().Be(0); + settings.Hardforks.ContainsKey(Hardfork.HF_Basilisk).Should().BeFalse(); + + // Check IsHardforkEnabled + + settings.IsHardforkEnabled(Hardfork.HF_Aspidochelone, 0).Should().BeTrue(); + settings.IsHardforkEnabled(Hardfork.HF_Aspidochelone, 10).Should().BeTrue(); + settings.IsHardforkEnabled(Hardfork.HF_Basilisk, 0).Should().BeFalse(); + settings.IsHardforkEnabled(Hardfork.HF_Basilisk, 10).Should().BeFalse(); + settings.IsHardforkEnabled(Hardfork.HF_Basilisk, 4120000).Should().BeFalse(); + } + + [TestMethod] + public void HardForkTestNone() + { + string json = CreateHKSettings(""); + + var file = Path.GetTempFileName(); + File.WriteAllText(file, json); + ProtocolSettings settings = ProtocolSettings.Load(file, false); + File.Delete(file); + + settings.Hardforks[Hardfork.HF_Aspidochelone].Should().Be(0); + settings.Hardforks[Hardfork.HF_Basilisk].Should().Be(0); + + // Check IsHardforkEnabled + + settings.IsHardforkEnabled(Hardfork.HF_Aspidochelone, 0).Should().BeTrue(); + settings.IsHardforkEnabled(Hardfork.HF_Aspidochelone, 10).Should().BeTrue(); + settings.IsHardforkEnabled(Hardfork.HF_Basilisk, 0).Should().BeTrue(); + settings.IsHardforkEnabled(Hardfork.HF_Basilisk, 10).Should().BeTrue(); + } + + [TestMethod] + public void HardForkTestAMoreThanB() + { + string json = CreateHKSettings("\"HF_Aspidochelone\": 4120001, \"HF_Basilisk\": 4120000"); + var file = Path.GetTempFileName(); + File.WriteAllText(file, json); + Assert.ThrowsException(() => ProtocolSettings.Load(file, false)); + File.Delete(file); + } + + internal static string CreateHKSettings(string hf) + { + return @" +{ + ""ProtocolConfiguration"": { + ""Network"": 860833102, + ""AddressVersion"": 53, + ""MillisecondsPerBlock"": 15000, + ""MaxTransactionsPerBlock"": 512, + ""MemoryPoolMaxTransactions"": 50000, + ""MaxTraceableBlocks"": 2102400, + ""Hardforks"": { + " + hf + @" + }, + ""InitialGasDistribution"": 5200000000000000, + ""ValidatorsCount"": 7, + ""StandbyCommittee"": [ + ""03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"", + ""02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093"", + ""03b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a"", + ""02ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba554"", + ""024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d"", + ""02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e"", + ""02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70"", + ""023a36c72844610b4d34d1968662424011bf783ca9d984efa19a20babf5582f3fe"", + ""03708b860c1de5d87f5b151a12c2a99feebd2e8b315ee8e7cf8aa19692a9e18379"", + ""03c6aa6e12638b36e88adc1ccdceac4db9929575c3e03576c617c49cce7114a050"", + ""03204223f8c86b8cd5c89ef12e4f0dbb314172e9241e30c9ef2293790793537cf0"", + ""02a62c915cf19c7f19a50ec217e79fac2439bbaad658493de0c7d8ffa92ab0aa62"", + ""03409f31f0d66bdc2f70a9730b66fe186658f84a8018204db01c106edc36553cd0"", + ""0288342b141c30dc8ffcde0204929bb46aed5756b41ef4a56778d15ada8f0c6654"", + ""020f2887f41474cfeb11fd262e982051c1541418137c02a0f4961af911045de639"", + ""0222038884bbd1d8ff109ed3bdef3542e768eef76c1247aea8bc8171f532928c30"", + ""03d281b42002647f0113f36c7b8efb30db66078dfaaa9ab3ff76d043a98d512fde"", + ""02504acbc1f4b3bdad1d86d6e1a08603771db135a73e61c9d565ae06a1938cd2ad"", + ""0226933336f1b75baa42d42b71d9091508b638046d19abd67f4e119bf64a7cfb4d"", + ""03cdcea66032b82f5c30450e381e5295cae85c5e6943af716cc6b646352a6067dc"", + ""02cd5a5547119e24feaa7c2a0f37b8c9366216bab7054de0065c9be42084003c8a"" + ], + ""SeedList"": [ + ""seed1.neo.org:10333"", + ""seed2.neo.org:10333"", + ""seed3.neo.org:10333"", + ""seed4.neo.org:10333"", + ""seed5.neo.org:10333"" + ] + } +} +"; } [TestMethod] public void TestGetSeedList() { - ProtocolSettings.Default.SeedList.Should().BeEquivalentTo(new string[] { "seed1.neo.org:10333", "seed2.neo.org:10333", "seed3.neo.org:10333", "seed4.neo.org:10333", "seed5.neo.org:10333", }); + TestProtocolSettings.Default.SeedList.Should().BeEquivalentTo(new string[] { "seed1.neo.org:10333", "seed2.neo.org:10333", "seed3.neo.org:10333", "seed4.neo.org:10333", "seed5.neo.org:10333", }); + } + + [TestMethod] + public void TestStandbyCommitteeAddressesFormat() + { + foreach (var point in TestProtocolSettings.Default.StandbyCommittee) + { + point.ToString().Should().MatchRegex("^[0-9A-Fa-f]{66}$"); // ECPoint is 66 hex characters + } + } + + [TestMethod] + public void TestValidatorsCount() + { + TestProtocolSettings.Default.StandbyCommittee.Count.Should().Be(TestProtocolSettings.Default.ValidatorsCount * 3); + } + + [TestMethod] + public void TestMaxTransactionsPerBlock() + { + TestProtocolSettings.Default.MaxTransactionsPerBlock.Should().BePositive().And.BeLessOrEqualTo(50000); // Assuming 50000 as a reasonable upper limit + } + + [TestMethod] + public void TestMaxTraceableBlocks() + { + TestProtocolSettings.Default.MaxTraceableBlocks.Should().BePositive(); + } + + [TestMethod] + public void TestInitialGasDistribution() + { + TestProtocolSettings.Default.InitialGasDistribution.Should().BeGreaterThan(0); + } + + [TestMethod] + public void TestHardforksSettings() + { + TestProtocolSettings.Default.Hardforks.Should().NotBeNull(); } [TestMethod] - public void TestNativeUpdateHistory() + public void TestAddressVersion() { - ProtocolSettings.Default.NativeUpdateHistory.Count.Should().Be(NativeContract.Contracts.Count); + TestProtocolSettings.Default.AddressVersion.Should().BeInRange(0, 255); // Address version is a byte + } + + [TestMethod] + public void TestNetworkSettingsConsistency() + { + TestProtocolSettings.Default.Network.Should().BePositive(); + TestProtocolSettings.Default.SeedList.Should().NotBeEmpty(); + } + + [TestMethod] + public void TestECPointParsing() + { + foreach (var point in TestProtocolSettings.Default.StandbyCommittee) + { + Action act = () => ECPoint.Parse(point.ToString(), ECCurve.Secp256r1); + act.Should().NotThrow(); + } + } + + [TestMethod] + public void TestSeedListFormatAndReachability() + { + foreach (var seed in TestProtocolSettings.Default.SeedList) + { + seed.Should().MatchRegex(@"^[\w.-]+:\d+$"); // Format: domain:port + } + } + + [TestMethod] + public void TestDefaultNetworkValue() + { + ProtocolSettings.Default.Network.Should().Be(0); + } + + [TestMethod] + public void TestDefaultAddressVersionValue() + { + TestProtocolSettings.Default.AddressVersion.Should().Be(ProtocolSettings.Default.AddressVersion); + } + + [TestMethod] + public void TestDefaultValidatorsCountValue() + { + ProtocolSettings.Default.ValidatorsCount.Should().Be(0); + } + + [TestMethod] + public void TestDefaultMillisecondsPerBlockValue() + { + TestProtocolSettings.Default.MillisecondsPerBlock.Should().Be(ProtocolSettings.Default.MillisecondsPerBlock); + } + + [TestMethod] + public void TestDefaultMaxTransactionsPerBlockValue() + { + TestProtocolSettings.Default.MaxTransactionsPerBlock.Should().Be(ProtocolSettings.Default.MaxTransactionsPerBlock); + } + + [TestMethod] + public void TestDefaultMemoryPoolMaxTransactionsValue() + { + TestProtocolSettings.Default.MemoryPoolMaxTransactions.Should().Be(ProtocolSettings.Default.MemoryPoolMaxTransactions); + } + + [TestMethod] + public void TestDefaultMaxTraceableBlocksValue() + { + TestProtocolSettings.Default.MaxTraceableBlocks.Should().Be(ProtocolSettings.Default.MaxTraceableBlocks); + } + + [TestMethod] + public void TestDefaultInitialGasDistributionValue() + { + TestProtocolSettings.Default.InitialGasDistribution.Should().Be(ProtocolSettings.Default.InitialGasDistribution); + } + + [TestMethod] + public void TestDefaultHardforksValue() + { + TestProtocolSettings.Default.Hardforks.Should().BeEquivalentTo(ProtocolSettings.Default.Hardforks); + } + + [TestMethod] + public void TestTimePerBlockCalculation() + { + var expectedTimeSpan = TimeSpan.FromMilliseconds(TestProtocolSettings.Default.MillisecondsPerBlock); + TestProtocolSettings.Default.TimePerBlock.Should().Be(expectedTimeSpan); + } + + [TestMethod] + public void TestLoad() + { + var loadedSetting = ProtocolSettings.Load("test.config.json", false); + + // Comparing all properties + TestProtocolSettings.Default.Network.Should().Be(loadedSetting.Network); + TestProtocolSettings.Default.AddressVersion.Should().Be(loadedSetting.AddressVersion); + TestProtocolSettings.Default.StandbyCommittee.Should().BeEquivalentTo(loadedSetting.StandbyCommittee); + TestProtocolSettings.Default.ValidatorsCount.Should().Be(loadedSetting.ValidatorsCount); + TestProtocolSettings.Default.SeedList.Should().BeEquivalentTo(loadedSetting.SeedList); + TestProtocolSettings.Default.MillisecondsPerBlock.Should().Be(loadedSetting.MillisecondsPerBlock); + TestProtocolSettings.Default.MaxTransactionsPerBlock.Should().Be(loadedSetting.MaxTransactionsPerBlock); + TestProtocolSettings.Default.MemoryPoolMaxTransactions.Should().Be(loadedSetting.MemoryPoolMaxTransactions); + TestProtocolSettings.Default.MaxTraceableBlocks.Should().Be(loadedSetting.MaxTraceableBlocks); + TestProtocolSettings.Default.InitialGasDistribution.Should().Be(loadedSetting.InitialGasDistribution); + TestProtocolSettings.Default.Hardforks.Should().BeEquivalentTo(loadedSetting.Hardforks); + + // If StandbyValidators is a derived property, comparing it as well + TestProtocolSettings.Default.StandbyValidators.Should().BeEquivalentTo(loadedSetting.StandbyValidators); } } } diff --git a/tests/Neo.UnitTests/UT_UInt160.cs b/tests/Neo.UnitTests/UT_UInt160.cs index 701eec0408..4799a80d19 100644 --- a/tests/Neo.UnitTests/UT_UInt160.cs +++ b/tests/Neo.UnitTests/UT_UInt160.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_UInt160.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + #pragma warning disable CS1718 using FluentAssertions; diff --git a/tests/Neo.UnitTests/UT_UInt256.cs b/tests/Neo.UnitTests/UT_UInt256.cs index 1caa047a09..b2bd02dac3 100644 --- a/tests/Neo.UnitTests/UT_UInt256.cs +++ b/tests/Neo.UnitTests/UT_UInt256.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_UInt256.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + #pragma warning disable CS1718 using FluentAssertions; diff --git a/tests/Neo.UnitTests/UT_UIntBenchmarks.cs b/tests/Neo.UnitTests/UT_UIntBenchmarks.cs index 882b5647ae..460588e1f1 100644 --- a/tests/Neo.UnitTests/UT_UIntBenchmarks.cs +++ b/tests/Neo.UnitTests/UT_UIntBenchmarks.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_UIntBenchmarks.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; diff --git a/tests/Neo.UnitTests/VM/UT_Helper.cs b/tests/Neo.UnitTests/VM/UT_Helper.cs index 539c5df028..c5bf61d152 100644 --- a/tests/Neo.UnitTests/VM/UT_Helper.cs +++ b/tests/Neo.UnitTests/VM/UT_Helper.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Helper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; @@ -29,11 +40,13 @@ public void TestEmit() [TestMethod] public void TestToJson() { - var item = new VM.Types.Array(); - item.Add(5); - item.Add("hello world"); - item.Add(new byte[] { 1, 2, 3 }); - item.Add(true); + var item = new VM.Types.Array + { + 5, + "hello world", + new byte[] { 1, 2, 3 }, + true + }; Assert.AreEqual("{\"type\":\"Integer\",\"value\":\"5\"}", item[0].ToJson().ToString()); Assert.AreEqual("{\"type\":\"ByteString\",\"value\":\"aGVsbG8gd29ybGQ=\"}", item[1].ToJson().ToString()); @@ -263,9 +276,11 @@ private void TestEmitPush2Array() { ScriptBuilder sb = new ScriptBuilder(); ContractParameter parameter = new ContractParameter(ContractParameterType.Array); - IList values = new List(); - values.Add(new ContractParameter(ContractParameterType.Integer)); - values.Add(new ContractParameter(ContractParameterType.Integer)); + IList values = new List + { + new ContractParameter(ContractParameterType.Integer), + new ContractParameter(ContractParameterType.Integer) + }; parameter.Value = values; sb.EmitPush(parameter); byte[] tempArray = new byte[4]; diff --git a/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Account.cs b/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Account.cs index 3090752ee8..65ec90ab7e 100644 --- a/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Account.cs +++ b/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Account.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_NEP6Account.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; @@ -23,7 +34,7 @@ public static void ClassSetup(TestContext ctx) byte[] privateKey = { 0x01,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}; _keyPair = new KeyPair(privateKey); - _nep2 = _keyPair.Export("Satoshi", ProtocolSettings.Default.AddressVersion, 2, 1, 1); + _nep2 = _keyPair.Export("Satoshi", TestProtocolSettings.Default.AddressVersion, 2, 1, 1); } [TestInitialize] @@ -86,7 +97,7 @@ public void TestFromJson() json["contract"] = null; json["extra"] = null; NEP6Account account = NEP6Account.FromJson(json, _wallet); - account.ScriptHash.Should().Be("NdtB8RXRmJ7Nhw1FPTm7E6HoDZGnDw37nf".ToScriptHash(ProtocolSettings.Default.AddressVersion)); + account.ScriptHash.Should().Be("NdtB8RXRmJ7Nhw1FPTm7E6HoDZGnDw37nf".ToScriptHash(TestProtocolSettings.Default.AddressVersion)); account.Label.Should().BeNull(); account.IsDefault.Should().BeTrue(); account.Lock.Should().BeFalse(); diff --git a/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Contract.cs b/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Contract.cs index 73262e390e..37f816c240 100644 --- a/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Contract.cs +++ b/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Contract.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_NEP6Contract.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Json; diff --git a/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Wallet.cs b/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Wallet.cs index bd8e087aa7..71914c86f0 100644 --- a/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Wallet.cs +++ b/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Wallet.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_NEP6Wallet.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Json; @@ -8,6 +19,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using Contract = Neo.SmartContract.Contract; @@ -41,7 +53,7 @@ public static void ClassInit(TestContext context) } keyPair = new KeyPair(privateKey); testScriptHash = Contract.CreateSignatureContract(keyPair.PublicKey).ScriptHash; - nep2key = keyPair.Export("123", ProtocolSettings.Default.AddressVersion, 2, 1, 1); + nep2key = keyPair.Export("123", TestProtocolSettings.Default.AddressVersion, 2, 1, 1); } private string CreateWalletFile() @@ -77,10 +89,10 @@ public void TestCreateAccount() Script = new byte[1], Signers = new Signer[] { new Signer() { Account = acc.ScriptHash } }, }; - var ctx = new ContractParametersContext(TestBlockchain.GetTestSnapshot(), tx, ProtocolSettings.Default.Network); + var ctx = new ContractParametersContext(TestBlockchain.GetTestSnapshot(), tx, TestProtocolSettings.Default.Network); Assert.IsTrue(uut.Sign(ctx)); tx.Witnesses = ctx.GetWitnesses(); - Assert.IsTrue(tx.VerifyWitnesses(ProtocolSettings.Default, TestBlockchain.GetTestSnapshot(), long.MaxValue)); + Assert.IsTrue(tx.VerifyWitnesses(TestProtocolSettings.Default, TestBlockchain.GetTestSnapshot(), long.MaxValue)); Assert.ThrowsException(() => uut.CreateAccount((byte[])null)); Assert.ThrowsException(() => uut.CreateAccount("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551".HexToBytes())); } @@ -95,7 +107,7 @@ public void TestChangePassword() wallet["accounts"] = new JArray(); wallet["extra"] = new JObject(); File.WriteAllText(wPath, wallet.ToString()); - uut = new NEP6Wallet(wPath, "123", ProtocolSettings.Default); + uut = new NEP6Wallet(wPath, "123", TestProtocolSettings.Default); uut.CreateAccount(keyPair.PrivateKey); uut.ChangePassword("456", "123").Should().BeFalse(); uut.ChangePassword("123", "456").Should().BeTrue(); @@ -106,11 +118,11 @@ public void TestChangePassword() [TestMethod] public void TestConstructorWithPathAndName() { - NEP6Wallet wallet = new(wPath, "123", ProtocolSettings.Default); + NEP6Wallet wallet = new(wPath, "123", TestProtocolSettings.Default); Assert.AreEqual("name", wallet.Name); Assert.AreEqual(new ScryptParameters(2, 1, 1).ToJson().ToString(), wallet.Scrypt.ToJson().ToString()); Assert.AreEqual(new Version("1.0").ToString(), wallet.Version.ToString()); - wallet = new NEP6Wallet("", "123", ProtocolSettings.Default, "test"); + wallet = new NEP6Wallet("", "123", TestProtocolSettings.Default, "test"); Assert.AreEqual("test", wallet.Name); Assert.AreEqual(ScryptParameters.Default.ToJson().ToString(), wallet.Scrypt.ToJson().ToString()); Assert.AreEqual(Version.Parse("1.0"), wallet.Version); @@ -126,7 +138,7 @@ public void TestConstructorWithJObject() wallet["accounts"] = new JArray(); wallet["extra"] = new JObject(); wallet.ToString().Should().Be("{\"name\":\"test\",\"version\":\"1.0\",\"scrypt\":{\"n\":16384,\"r\":8,\"p\":8},\"accounts\":[],\"extra\":{}}"); - NEP6Wallet w = new(null, "123", ProtocolSettings.Default, wallet); + NEP6Wallet w = new(null, "123", TestProtocolSettings.Default, wallet); Assert.AreEqual("test", w.Name); Assert.AreEqual(Version.Parse("1.0").ToString(), w.Version.ToString()); } @@ -291,6 +303,11 @@ public void TestImportCert() X509Certificate2 cert = NewCertificate(); Assert.IsNotNull(cert); Assert.AreEqual(true, cert.HasPrivateKey); + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + Assert.ThrowsException(() => uut.Import(cert)); + return; + } WalletAccount account = uut.Import(cert); Assert.IsNotNull(account); } diff --git a/tests/Neo.UnitTests/Wallets/NEP6/UT_ScryptParameters.cs b/tests/Neo.UnitTests/Wallets/NEP6/UT_ScryptParameters.cs index 0c3aba328c..8a468f9f41 100644 --- a/tests/Neo.UnitTests/Wallets/NEP6/UT_ScryptParameters.cs +++ b/tests/Neo.UnitTests/Wallets/NEP6/UT_ScryptParameters.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ScryptParameters.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Json; diff --git a/tests/Neo.UnitTests/Wallets/UT_AssetDescriptor.cs b/tests/Neo.UnitTests/Wallets/UT_AssetDescriptor.cs index d87811cf7f..8d938492bc 100644 --- a/tests/Neo.UnitTests/Wallets/UT_AssetDescriptor.cs +++ b/tests/Neo.UnitTests/Wallets/UT_AssetDescriptor.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_AssetDescriptor.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.SmartContract.Native; @@ -14,7 +25,7 @@ public void TestConstructorWithNonexistAssetId() var snapshot = TestBlockchain.GetTestSnapshot(); Action action = () => { - var descriptor = new Neo.Wallets.AssetDescriptor(snapshot, ProtocolSettings.Default, UInt160.Parse("01ff00ff00ff00ff00ff00ff00ff00ff00ff00a4")); + var descriptor = new Neo.Wallets.AssetDescriptor(snapshot, TestProtocolSettings.Default, UInt160.Parse("01ff00ff00ff00ff00ff00ff00ff00ff00ff00a4")); }; action.Should().Throw(); } @@ -23,7 +34,7 @@ public void TestConstructorWithNonexistAssetId() public void Check_GAS() { var snapshot = TestBlockchain.GetTestSnapshot(); - var descriptor = new Neo.Wallets.AssetDescriptor(snapshot, ProtocolSettings.Default, NativeContract.GAS.Hash); + var descriptor = new Neo.Wallets.AssetDescriptor(snapshot, TestProtocolSettings.Default, NativeContract.GAS.Hash); descriptor.AssetId.Should().Be(NativeContract.GAS.Hash); descriptor.AssetName.Should().Be(nameof(GasToken)); descriptor.ToString().Should().Be(nameof(GasToken)); @@ -35,7 +46,7 @@ public void Check_GAS() public void Check_NEO() { var snapshot = TestBlockchain.GetTestSnapshot(); - var descriptor = new Neo.Wallets.AssetDescriptor(snapshot, ProtocolSettings.Default, NativeContract.NEO.Hash); + var descriptor = new Neo.Wallets.AssetDescriptor(snapshot, TestProtocolSettings.Default, NativeContract.NEO.Hash); descriptor.AssetId.Should().Be(NativeContract.NEO.Hash); descriptor.AssetName.Should().Be(nameof(NeoToken)); descriptor.ToString().Should().Be(nameof(NeoToken)); diff --git a/tests/Neo.UnitTests/Wallets/UT_KeyPair.cs b/tests/Neo.UnitTests/Wallets/UT_KeyPair.cs index 5e5996ff0a..7ae84960c7 100644 --- a/tests/Neo.UnitTests/Wallets/UT_KeyPair.cs +++ b/tests/Neo.UnitTests/Wallets/UT_KeyPair.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_KeyPair.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; @@ -69,7 +80,7 @@ public void TestEqualsWithObj() for (int i = 0; i < privateKey.Length; i++) privateKey[i] = (byte)random.Next(256); KeyPair keyPair = new KeyPair(privateKey); - Object keyPair2 = keyPair as Object; + Object keyPair2 = keyPair; keyPair.Equals(keyPair2).Should().BeTrue(); } diff --git a/tests/Neo.UnitTests/Wallets/UT_Wallet.cs b/tests/Neo.UnitTests/Wallets/UT_Wallet.cs index bf42ad7cd0..d134c6aed3 100644 --- a/tests/Neo.UnitTests/Wallets/UT_Wallet.cs +++ b/tests/Neo.UnitTests/Wallets/UT_Wallet.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Wallet.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; @@ -20,7 +31,7 @@ internal class MyWallet : Wallet private readonly Dictionary accounts = new(); - public MyWallet() : base(null, ProtocolSettings.Default) + public MyWallet() : base(null, TestProtocolSettings.Default) { } @@ -112,7 +123,7 @@ public class UT_Wallet public static void ClassInit(TestContext ctx) { glkey = UT_Crypto.GenerateCertainKey(32); - nep2Key = glkey.Export("pwd", ProtocolSettings.Default.AddressVersion, 2, 1, 1); + nep2Key = glkey.Export("pwd", TestProtocolSettings.Default.AddressVersion, 2, 1, 1); } [TestMethod] @@ -210,13 +221,11 @@ public void TestGetAvailable() var key = NativeContract.GAS.CreateStorageKey(20, account.ScriptHash); var entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 10000 * NativeContract.GAS.Factor; - snapshot.Commit(); wallet.GetAvailable(snapshot, NativeContract.GAS.Hash).Should().Be(new BigDecimal(new BigInteger(1000000000000M), 8)); entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 0; - snapshot.Commit(); } [TestMethod] @@ -232,14 +241,12 @@ public void TestGetBalance() var key = NativeContract.GAS.CreateStorageKey(20, account.ScriptHash); var entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 10000 * NativeContract.GAS.Factor; - snapshot.Commit(); wallet.GetBalance(snapshot, UInt160.Zero, new UInt160[] { account.ScriptHash }).Should().Be(new BigDecimal(BigInteger.Zero, 0)); wallet.GetBalance(snapshot, NativeContract.GAS.Hash, new UInt160[] { account.ScriptHash }).Should().Be(new BigDecimal(new BigInteger(1000000000000M), 8)); entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 0; - snapshot.Commit(); } [TestMethod] @@ -334,8 +341,6 @@ public void TestMakeTransaction1() var entry2 = snapshot.GetAndChange(key, () => new StorageItem(new NeoToken.NeoAccountState())); entry2.GetInteroperable().Balance = 10000 * NativeContract.NEO.Factor; - snapshot.Commit(); - var tx = wallet.MakeTransaction(snapshot, new TransferOutput[] { new TransferOutput() @@ -363,7 +368,6 @@ public void TestMakeTransaction1() entry2 = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); entry1.GetInteroperable().Balance = 0; entry2.GetInteroperable().Balance = 0; - snapshot.Commit(); } [TestMethod] @@ -382,7 +386,6 @@ public void TestMakeTransaction2() var key = NativeContract.GAS.CreateStorageKey(20, account.ScriptHash); var entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 1000000 * NativeContract.GAS.Factor; - snapshot.Commit(); var tx = wallet.MakeTransaction(snapshot, Array.Empty(), account.ScriptHash, new[]{ new Signer() { @@ -397,7 +400,6 @@ public void TestMakeTransaction2() entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 0; - snapshot.Commit(); } [TestMethod] diff --git a/tests/Neo.UnitTests/Wallets/UT_WalletAccount.cs b/tests/Neo.UnitTests/Wallets/UT_WalletAccount.cs index 13ba4d113f..26a8464a7f 100644 --- a/tests/Neo.UnitTests/Wallets/UT_WalletAccount.cs +++ b/tests/Neo.UnitTests/Wallets/UT_WalletAccount.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_WalletAccount.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.SmartContract; @@ -11,7 +22,7 @@ public class MyWalletAccount : WalletAccount public override bool HasKey => key != null; public MyWalletAccount(UInt160 scriptHash) - : base(scriptHash, ProtocolSettings.Default) + : base(scriptHash, TestProtocolSettings.Default) { } diff --git a/tests/Neo.UnitTests/Wallets/UT_Wallets_Helper.cs b/tests/Neo.UnitTests/Wallets/UT_Wallets_Helper.cs index 8c5f23f5a4..843eeae192 100644 --- a/tests/Neo.UnitTests/Wallets/UT_Wallets_Helper.cs +++ b/tests/Neo.UnitTests/Wallets/UT_Wallets_Helper.cs @@ -1,3 +1,14 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Wallets_Helper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; @@ -15,9 +26,9 @@ public void TestToScriptHash() { byte[] array = { 0x01 }; UInt160 scriptHash = new UInt160(Crypto.Hash160(array)); - "NdtB8RXRmJ7Nhw1FPTm7E6HoDZGnDw37nf".ToScriptHash(ProtocolSettings.Default.AddressVersion).Should().Be(scriptHash); + "NdtB8RXRmJ7Nhw1FPTm7E6HoDZGnDw37nf".ToScriptHash(TestProtocolSettings.Default.AddressVersion).Should().Be(scriptHash); - Action action = () => "3vQB7B6MrGQZaxCuFg4oh".ToScriptHash(ProtocolSettings.Default.AddressVersion); + Action action = () => "3vQB7B6MrGQZaxCuFg4oh".ToScriptHash(TestProtocolSettings.Default.AddressVersion); action.Should().Throw(); var address = scriptHash.ToAddress(ProtocolSettings.Default.AddressVersion); diff --git a/tests/Neo.UnitTests/test.config.json b/tests/Neo.UnitTests/test.config.json new file mode 100644 index 0000000000..0d2f885da6 --- /dev/null +++ b/tests/Neo.UnitTests/test.config.json @@ -0,0 +1,66 @@ +{ + "ApplicationConfiguration": { + "Logger": { + "Path": "Logs", + "ConsoleOutput": false, + "Active": false + }, + "Storage": { + "Engine": "LevelDBStore", + "Path": "Data_LevelDB_{0}" + }, + "P2P": { + "Port": 10333, + "WsPort": 10334, + "MinDesiredConnections": 10, + "MaxConnections": 40, + "MaxConnectionsPerAddress": 3 + }, + "UnlockWallet": { + "Path": "", + "Password": "", + "IsActive": false + } + }, + "ProtocolConfiguration": { + "Network": 860833102, + "AddressVersion": 53, + "MillisecondsPerBlock": 15000, + "MaxTransactionsPerBlock": 512, + "MemoryPoolMaxTransactions": 50000, + "MaxTraceableBlocks": 2102400, + "Hardforks": {}, + "InitialGasDistribution": 5200000000000000, + "ValidatorsCount": 7, + "StandbyCommittee": [ + "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", + "02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093", + "03b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a", + "02ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba554", + "024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d", + "02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e", + "02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70", + "023a36c72844610b4d34d1968662424011bf783ca9d984efa19a20babf5582f3fe", + "03708b860c1de5d87f5b151a12c2a99feebd2e8b315ee8e7cf8aa19692a9e18379", + "03c6aa6e12638b36e88adc1ccdceac4db9929575c3e03576c617c49cce7114a050", + "03204223f8c86b8cd5c89ef12e4f0dbb314172e9241e30c9ef2293790793537cf0", + "02a62c915cf19c7f19a50ec217e79fac2439bbaad658493de0c7d8ffa92ab0aa62", + "03409f31f0d66bdc2f70a9730b66fe186658f84a8018204db01c106edc36553cd0", + "0288342b141c30dc8ffcde0204929bb46aed5756b41ef4a56778d15ada8f0c6654", + "020f2887f41474cfeb11fd262e982051c1541418137c02a0f4961af911045de639", + "0222038884bbd1d8ff109ed3bdef3542e768eef76c1247aea8bc8171f532928c30", + "03d281b42002647f0113f36c7b8efb30db66078dfaaa9ab3ff76d043a98d512fde", + "02504acbc1f4b3bdad1d86d6e1a08603771db135a73e61c9d565ae06a1938cd2ad", + "0226933336f1b75baa42d42b71d9091508b638046d19abd67f4e119bf64a7cfb4d", + "03cdcea66032b82f5c30450e381e5295cae85c5e6943af716cc6b646352a6067dc", + "02cd5a5547119e24feaa7c2a0f37b8c9366216bab7054de0065c9be42084003c8a" + ], + "SeedList": [ + "seed1.neo.org:10333", + "seed2.neo.org:10333", + "seed3.neo.org:10333", + "seed4.neo.org:10333", + "seed5.neo.org:10333" + ] + } +} diff --git a/tests/Neo.VM.Tests/Converters/ScriptConverter.cs b/tests/Neo.VM.Tests/Converters/ScriptConverter.cs new file mode 100644 index 0000000000..064df4fb30 --- /dev/null +++ b/tests/Neo.VM.Tests/Converters/ScriptConverter.cs @@ -0,0 +1,162 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ScriptConverter.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Test.Extensions; +using Neo.VM; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Linq; + +namespace Neo.Test.Converters +{ + internal class ScriptConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return objectType == typeof(byte[]) || objectType == typeof(string); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + switch (reader.TokenType) + { + case JsonToken.String: + { + if (reader.Value is string str) + { + Assert.IsTrue(str.StartsWith("0x"), $"'0x' prefix required for value: '{str}'"); + return str.FromHexString(); + } + break; + } + case JsonToken.Bytes: + { + if (reader.Value is byte[] data) return data; + break; + } + case JsonToken.StartArray: + { + using var script = new ScriptBuilder(); + + foreach (var entry in JArray.Load(reader)) + { + var mul = 1; + var value = entry.Value(); + + if (Enum.IsDefined(typeof(OpCode), value) && Enum.TryParse(value, out var opCode)) + { + for (int x = 0; x < mul; x++) + { + script.Emit(opCode); + } + } + else + { + for (int x = 0; x < mul; x++) + { + Assert.IsTrue(value.StartsWith("0x"), $"'0x' prefix required for value: '{value}'"); + script.EmitRaw(value.FromHexString()); + } + } + } + + return script.ToArray(); + } + } + + throw new FormatException(); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value is byte[] data) + { + int ip = 0; + var array = new JArray(); + + try + { + for (ip = 0; ip < data.Length;) + { + var instruction = new Instruction(data, ip); + + array.Add(instruction.OpCode.ToString().ToUpperInvariant()); + + // Operand Size + + if (instruction.Size - 1 - instruction.Operand.Length > 0) + { + array.Add(data.Skip(ip + 1).Take(instruction.Size - 1 - instruction.Operand.Length).ToArray().ToHexString()); + } + + if (!instruction.Operand.IsEmpty) + { + // Data + + array.Add(instruction.Operand.ToArray().ToHexString()); + } + + ip += instruction.Size; + } + } + catch + { + // Something was wrong, but maybe it's intentioned + + if (Enum.IsDefined(typeof(OpCode), data[ip])) + { + // Check if it was the content and not the opcode + + array.Add(((OpCode)data[ip]).ToString().ToUpperInvariant()); + array.Add(data[(ip + 1)..].ToHexString()); + } + else + { + array.Add(data[ip..].ToHexString()); + } + } + + // Write the script + + writer.WriteStartArray(); + foreach (var entry in array) writer.WriteValue(entry.Value()); + writer.WriteEndArray(); + + // Double check - Ensure that the format is exactly the same + + using var script = new ScriptBuilder(); + + foreach (var entry in array) + { + if (Enum.TryParse(entry.Value(), out var opCode)) + { + script.Emit(opCode); + } + else + { + script.EmitRaw(entry.Value().FromHexString()); + } + } + + if (script.ToArray().ToHexString() != data.ToHexString()) + { + throw new FormatException(); + } + } + else + { + throw new FormatException(); + } + } + } +} diff --git a/tests/Neo.VM.Tests/Converters/UppercaseEnum.cs b/tests/Neo.VM.Tests/Converters/UppercaseEnum.cs new file mode 100644 index 0000000000..0da336e698 --- /dev/null +++ b/tests/Neo.VM.Tests/Converters/UppercaseEnum.cs @@ -0,0 +1,34 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UppercaseEnum.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Newtonsoft.Json; +using System; + +namespace Neo.Test.Converters +{ + internal class UppercaseEnum : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return objectType.IsEnum; + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + return Enum.Parse(objectType, reader.Value.ToString(), true); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + writer.WriteValue(value.ToString().ToUpperInvariant()); + } + } +} diff --git a/tests/Neo.VM.Tests/Extensions/JsonExtensions.cs b/tests/Neo.VM.Tests/Extensions/JsonExtensions.cs new file mode 100644 index 0000000000..01dd057ca8 --- /dev/null +++ b/tests/Neo.VM.Tests/Extensions/JsonExtensions.cs @@ -0,0 +1,58 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// JsonExtensions.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Serialization; + +namespace Neo.Test.Extensions +{ + public static class JsonExtensions + { + private static readonly JsonSerializerSettings _settings; + + /// + /// Static constructor + /// + static JsonExtensions() + { + _settings = new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver(), + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + }; + + _settings.Converters.Add(new StringEnumConverter(new CamelCaseNamingStrategy())); + } + + /// + /// Deserialize json to object + /// + /// Type + /// Json + /// Unit test + public static T DeserializeJson(this string input) + { + return JsonConvert.DeserializeObject(input, _settings); + } + + /// + /// Serialize UT to json + /// + /// Unit test + /// Json + public static string ToJson(this object ut) + { + return JsonConvert.SerializeObject(ut, _settings); + } + } +} diff --git a/tests/Neo.VM.Tests/Extensions/StringExtensions.cs b/tests/Neo.VM.Tests/Extensions/StringExtensions.cs new file mode 100644 index 0000000000..4263d271c4 --- /dev/null +++ b/tests/Neo.VM.Tests/Extensions/StringExtensions.cs @@ -0,0 +1,68 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// StringExtensions.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Globalization; + +namespace Neo.Test.Extensions +{ + internal static class StringExtensions + { + /// + /// Convert buffer to hex string + /// + /// Data + /// Return hex string + public static string ToHexString(this byte[] data) + { + if (data == null) return ""; + + var m = data.Length; + if (m == 0) return ""; + + var sb = new char[(m * 2) + 2]; + + sb[0] = '0'; + sb[1] = 'x'; + + for (int x = 0, y = 2; x < m; x++, y += 2) + { + var hex = data[x].ToString("x2"); + + sb[y] = hex[0]; + sb[y + 1] = hex[1]; + } + + return new string(sb); + } + + /// + /// Convert string in Hex format to byte array + /// + /// Hexadecimal string + /// Return byte array + public static byte[] FromHexString(this string value) + { + if (string.IsNullOrEmpty(value)) + return Array.Empty(); + if (value.StartsWith("0x")) + value = value[2..]; + if (value.Length % 2 == 1) + throw new FormatException(); + + var result = new byte[value.Length / 2]; + for (var i = 0; i < result.Length; i++) + result[i] = byte.Parse(value.Substring(i * 2, 2), NumberStyles.AllowHexSpecifier); + + return result; + } + } +} diff --git a/tests/Neo.VM.Tests/Helpers/RandomHelper.cs b/tests/Neo.VM.Tests/Helpers/RandomHelper.cs new file mode 100644 index 0000000000..77dc999145 --- /dev/null +++ b/tests/Neo.VM.Tests/Helpers/RandomHelper.cs @@ -0,0 +1,50 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RandomHelper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; + +namespace Neo.Test.Helpers +{ + public class RandomHelper + { + private const string _randchars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + private static readonly Random _rand = new(); + + /// + /// Get random buffer + /// + /// Length + /// Buffer + public static byte[] RandBuffer(int length) + { + var buffer = new byte[length]; + _rand.NextBytes(buffer); + return buffer; + } + + /// + /// Get random string + /// + /// Length + /// Buffer + public static string RandString(int length) + { + var stringChars = new char[length]; + + for (int i = 0; i < stringChars.Length; i++) + { + stringChars[i] = _randchars[_rand.Next(_randchars.Length)]; + } + + return new string(stringChars); + } + } +} diff --git a/tests/Neo.VM.Tests/Neo.VM.Tests.csproj b/tests/Neo.VM.Tests/Neo.VM.Tests.csproj new file mode 100644 index 0000000000..53c16ed4af --- /dev/null +++ b/tests/Neo.VM.Tests/Neo.VM.Tests.csproj @@ -0,0 +1,25 @@ + + + + net8.0 + Neo.Test + true + + + + + + + + + PreserveNewest + + + + + + + + + + diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/GE.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/GE.json new file mode 100644 index 0000000000..d70ebf3be6 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/GE.json @@ -0,0 +1,443 @@ +{ + "category": "Numeric", + "name": "GE same types", + "tests": [ + { + "name": "Without two pushes", + "script": [ + "GE" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without one push", + "script": [ + "PUSH0", + "GE" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "GE", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + } + ] + }, + { + "name": "Real test [0,0]", + "script": [ + "PUSH0", + "PUSH0", + "GE" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "GE", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [1,0]", + "script": [ + "PUSH1", + "PUSH0", + "GE" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "GE", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [0,1]", + "script": [ + "PUSH0", + "PUSH1", + "GE" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "GE", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [null,1]", + "script": [ + "PUSHNULL", + "PUSH1", + "GE" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "GE", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "null" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [1,null]", + "script": [ + "PUSH1", + "PUSHNULL", + "GE" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "GE", + "evaluationStack": [ + { + "type": "null" + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "FAULT test [1,array]", + "script": [ + "PUSH1", + "NEWARRAY0", + "GE" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "GE", + "evaluationStack": [ + { + "type": "array", + "value": [] + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT", + "resultStack": [ + { + "type": "boolean", + "value": false + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/GT.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/GT.json new file mode 100644 index 0000000000..edc0767b3c --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/GT.json @@ -0,0 +1,443 @@ +{ + "category": "Numeric", + "name": "GT same types", + "tests": [ + { + "name": "Without two pushes", + "script": [ + "GT" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without one push", + "script": [ + "PUSH0", + "GT" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "GT", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + } + ] + }, + { + "name": "Real test [0,0]", + "script": [ + "PUSH0", + "PUSH0", + "GT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "GT", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [1,0]", + "script": [ + "PUSH1", + "PUSH0", + "GT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "GT", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [0,1]", + "script": [ + "PUSH0", + "PUSH1", + "GT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "GT", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [null,1]", + "script": [ + "PUSHNULL", + "PUSH1", + "GT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "GT", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "null" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [1,null]", + "script": [ + "PUSH1", + "PUSHNULL", + "GT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "GT", + "evaluationStack": [ + { + "type": "null" + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "FAULT test [1,array]", + "script": [ + "PUSH1", + "NEWARRAY0", + "GT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "GT", + "evaluationStack": [ + { + "type": "array", + "value": [] + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT", + "resultStack": [ + { + "type": "boolean", + "value": false + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/LE.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/LE.json new file mode 100644 index 0000000000..eb85215322 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/LE.json @@ -0,0 +1,443 @@ +{ + "category": "Numeric", + "name": "LE same types", + "tests": [ + { + "name": "Without two pushes", + "script": [ + "LE" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without one push", + "script": [ + "PUSH0", + "LE" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "LE", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + } + ] + }, + { + "name": "Real test [0,0]", + "script": [ + "PUSH0", + "PUSH0", + "LE" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "LE", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [1,0]", + "script": [ + "PUSH1", + "PUSH0", + "LE" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "LE", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [0,1]", + "script": [ + "PUSH0", + "PUSH1", + "LE" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "LE", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [null,1]", + "script": [ + "PUSHNULL", + "PUSH1", + "LE" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "LE", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "null" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [1,null]", + "script": [ + "PUSH1", + "PUSHNULL", + "LE" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "LE", + "evaluationStack": [ + { + "type": "null" + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "FAULT test [1,array]", + "script": [ + "PUSH1", + "NEWARRAY0", + "LE" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "LE", + "evaluationStack": [ + { + "type": "array", + "value": [] + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT", + "resultStack": [ + { + "type": "boolean", + "value": false + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/LT.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/LT.json new file mode 100644 index 0000000000..ed2b6fac01 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/LT.json @@ -0,0 +1,443 @@ +{ + "category": "Numeric", + "name": "LT same types", + "tests": [ + { + "name": "Without two pushes", + "script": [ + "LT" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without one push", + "script": [ + "PUSH0", + "LT" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "LT", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + } + ] + }, + { + "name": "Real test [0,0]", + "script": [ + "PUSH0", + "PUSH0", + "LT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "LT", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [1,0]", + "script": [ + "PUSH1", + "PUSH0", + "LT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "LT", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [0,1]", + "script": [ + "PUSH0", + "PUSH1", + "LT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "LT", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [null,1]", + "script": [ + "PUSHNULL", + "PUSH1", + "LT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "LT", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "null" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [1,null]", + "script": [ + "PUSH1", + "PUSHNULL", + "LT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "LT", + "evaluationStack": [ + { + "type": "null" + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "FAULT test [1,array]", + "script": [ + "PUSH1", + "NEWARRAY0", + "LT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "LT", + "evaluationStack": [ + { + "type": "array", + "value": [] + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT", + "resultStack": [ + { + "type": "boolean", + "value": false + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/MODMUL.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/MODMUL.json new file mode 100644 index 0000000000..e3b93bfb56 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/MODMUL.json @@ -0,0 +1,141 @@ +{ + "category": "Numeric", + "name": "MODMUL", + "tests": [ + { + "name": "Exception - Without items", + "script": [ + "MODMUL" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "MODMUL", + "evaluationStack": [] + } + ] + } + } + ] + }, + { + "name": "Real test (8 * 2 % 3)", + "script": [ + "PUSH8", + "PUSH2", + "PUSH3", + "MODMUL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "MODMUL", + "evaluationStack": [ + { + "type": "Integer", + "value": 3 + }, + { + "type": "Integer", + "value": 2 + }, + { + "type": "Integer", + "value": 8 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "invocationStack": [], + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + }, + { + "name": "Real test (16 * 2 % 4)", + "script": [ + "PUSH16", + "PUSH2", + "PUSH4", + "MODMUL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "MODMUL", + "evaluationStack": [ + { + "type": "Integer", + "value": 4 + }, + { + "type": "Integer", + "value": 2 + }, + { + "type": "Integer", + "value": 16 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "invocationStack": [], + "resultStack": [ + { + "type": "Integer", + "value": 0 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/MODPOW.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/MODPOW.json new file mode 100644 index 0000000000..68efa97efa --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/MODPOW.json @@ -0,0 +1,145 @@ +{ + "category": "Numeric", + "name": "MODPOW", + "tests": [ + { + "name": "Exception - Without items", + "script": [ + "MODPOW" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "MODPOW", + "evaluationStack": [] + } + ] + } + } + ] + }, + { + "name": "Real test (19 ModInverse 141 = 52)", + "script": [ + "PUSHINT8", + "0x13", + "PUSHM1", + "PUSHINT16", + "0x8d00", + "MODPOW" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "MODPOW", + "evaluationStack": [ + { + "type": "Integer", + "value": 141 + }, + { + "type": "Integer", + "value": -1 + }, + { + "type": "Integer", + "value": 19 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "invocationStack": [], + "resultStack": [ + { + "type": "Integer", + "value": 52 + } + ] + } + } + ] + }, + { + "name": "Real test (ModPow 19, 2, 141 = 79)", + "script": [ + "PUSHINT8", + "0x13", + "PUSH2", + "PUSHINT16", + "0x8d00", + "MODPOW" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "MODPOW", + "evaluationStack": [ + { + "type": "Integer", + "value": 141 + }, + { + "type": "Integer", + "value": 2 + }, + { + "type": "Integer", + "value": 19 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "invocationStack": [], + "resultStack": [ + { + "type": "Integer", + "value": 79 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/NOT.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/NOT.json new file mode 100644 index 0000000000..72d36c6fc7 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/NOT.json @@ -0,0 +1,537 @@ +{ + "category": "Numeric", + "name": "NOT", + "tests": [ + { + "name": "Without push", + "script": [ + "NOT" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "With bool", + "script": [ + "PUSH1", + "NOT", + "NOT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "NOT", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "With integer 0x03", + "script": [ + "PUSH3", + "NOT" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "NOT", + "evaluationStack": [ + { + "type": "integer", + "value": 3 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "With map", + "script": [ + "NEWMAP", + "NOT" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "NOT", + "evaluationStack": [ + { + "type": "map", + "value": {} + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "With array", + "script": [ + "PUSH0", + "NEWARRAY", + "NOT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "NOT", + "evaluationStack": [ + { + "type": "array", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "With struct", + "script": [ + "PUSH0", + "NEWSTRUCT", + "NOT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "NOT", + "evaluationStack": [ + { + "type": "struct", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "With byte array 0x0000", + "script": [ + "PUSHDATA1", + "0x02", + "0x0000", + "NOT" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "NOT", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x0000" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "With byte array 0x0001", + "script": [ + "PUSHDATA1", + "0x02", + "0x0001", + "NOT" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "NOT", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x0001" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "With interop", + "script": [ + "SYSCALL", + "0x77777777", + "NOT" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "NOT", + "evaluationStack": [ + { + "type": "interop", + "value": "Object" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/NUMEQUAL.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/NUMEQUAL.json new file mode 100644 index 0000000000..613a2bd31d --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/NUMEQUAL.json @@ -0,0 +1,1058 @@ +{ + "category": "Numeric", + "name": "NUMEQUAL same types", + "tests": [ + { + "name": "Without two pushes", + "script": [ + "NUMEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without one push", + "script": [ + "PUSH0", + "NUMEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "NUMEQUAL", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + } + ] + }, + { + "name": "Real test [Bool,Bool]=true", + "script": [ + "PUSH0", + "NOT", + "PUSH0", + "NOT", + "NUMEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "NUMEQUAL", + "evaluationStack": [ + { + "type": "boolean", + "value": true + }, + { + "type": "boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [Bool,Bool]=false", + "script": [ + "PUSH0", + "NOT", + "PUSH0", + "NOT", + "NOT", + "NUMEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "NUMEQUAL", + "evaluationStack": [ + { + "type": "boolean", + "value": false + }, + { + "type": "boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [Integer,Integer]=true", + "script": [ + "PUSH1", + "PUSH1", + "NUMEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "NUMEQUAL", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [Integer,Integer]=false", + "script": [ + "PUSH2", + "PUSH1", + "NUMEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "NUMEQUAL", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 2 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [Array,Array]=Fault", + "script": [ + "PUSH0", + "NEWARRAY", + "DUP", + "NUMEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "NUMEQUAL", + "evaluationStack": [ + { + "type": "array", + "value": [] + }, + { + "type": "array", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Array,Array]=fault", + "script": [ + "PUSH0", + "NEWARRAY", + "PUSH0", + "NEWARRAY", + "NUMEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "NUMEQUAL", + "evaluationStack": [ + { + "type": "array", + "value": [] + }, + { + "type": "array", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Struct,Struct]=fault with clone", + "script": [ + "PUSH0", + "NEWSTRUCT", + "DUP", + "NUMEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "NUMEQUAL", + "evaluationStack": [ + { + "type": "struct", + "value": [] + }, + { + "type": "struct", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Struct,Struct]=fault without clone", + "script": [ + "PUSH0", + "NEWSTRUCT", + "PUSH0", + "NEWSTRUCT", + "NUMEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "NUMEQUAL", + "evaluationStack": [ + { + "type": "struct", + "value": [] + }, + { + "type": "struct", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Struct,Struct]=fault", + "script": [ + "PUSH0", + "NEWSTRUCT", + "PUSH1", + "NEWSTRUCT", + "NUMEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "NUMEQUAL", + "evaluationStack": [ + { + "type": "struct", + "value": [ + { + "type": "Null" + } + ] + }, + { + "type": "struct", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Map,Map]=Fault", + "script": [ + "NEWMAP", + "DUP", + "NUMEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "NUMEQUAL", + "evaluationStack": [ + { + "type": "map", + "value": {} + }, + { + "type": "map", + "value": {} + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Map,Map]=Fault", + "script": [ + "NEWMAP", + "NEWMAP", + "NUMEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "NUMEQUAL", + "evaluationStack": [ + { + "type": "map", + "value": {} + }, + { + "type": "map", + "value": {} + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [ByteString,ByteString]=true", + "script": [ + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "NUMEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 38, + "nextInstruction": "NUMEQUAL", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + }, + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 39, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [ByteString,ByteString]=false same length", + "script": [ + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000124", + "NUMEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 38, + "nextInstruction": "NUMEQUAL", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000124" + }, + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 39, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [ByteString,ByteString]=false different length", + "script": [ + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "PUSHDATA1", + "0x10", + "0x00000000000000000000000000000124", + "NUMEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 37, + "nextInstruction": "NUMEQUAL", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x00000000000000000000000000000124" + }, + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 38, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [ByteString,ByteString]=false different length", + "script": [ + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "PUSHDATA1", + "0x10", + "0x00000000000000000000000000000124", + "NUMEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 37, + "nextInstruction": "NUMEQUAL", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x00000000000000000000000000000124" + }, + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 38, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test with clone [Interop,Interop]=fault", + "script": [ + "SYSCALL", + "0x77777777", + "DUP", + "NUMEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "DUP", + "evaluationStack": [ + { + "type": "interop", + "value": "Object" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "NUMEQUAL", + "evaluationStack": [ + { + "type": "interop", + "value": "Object" + }, + { + "type": "interop", + "value": "Object" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Interop,Interop]=fault", + "script": [ + "SYSCALL", + "0x77777777", + "SYSCALL", + "0x77777777", + "NUMEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 10, + "nextInstruction": "NUMEQUAL", + "evaluationStack": [ + { + "type": "interop", + "value": "Object" + }, + { + "type": "interop", + "value": "Object" + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/NUMNOTEQUAL.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/NUMNOTEQUAL.json new file mode 100644 index 0000000000..386212cfed --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/NUMNOTEQUAL.json @@ -0,0 +1,1058 @@ +{ + "category": "Numeric", + "name": "NUMNOTEQUAL same types", + "tests": [ + { + "name": "Without two pushes", + "script": [ + "NUMNOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without one push", + "script": [ + "PUSH0", + "NUMNOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "NUMNOTEQUAL", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + } + ] + }, + { + "name": "Real test [Bool,Bool]=false", + "script": [ + "PUSH0", + "NOT", + "PUSH0", + "NOT", + "NUMNOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "NUMNOTEQUAL", + "evaluationStack": [ + { + "type": "boolean", + "value": true + }, + { + "type": "boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [Bool,Bool]=true", + "script": [ + "PUSH0", + "NOT", + "PUSH0", + "NOT", + "NOT", + "NUMNOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "NUMNOTEQUAL", + "evaluationStack": [ + { + "type": "boolean", + "value": false + }, + { + "type": "boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [Integer,Integer]=false", + "script": [ + "PUSH1", + "PUSH1", + "NUMNOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "NUMNOTEQUAL", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [Integer,Integer]=true", + "script": [ + "PUSH2", + "PUSH1", + "NUMNOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "NUMNOTEQUAL", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 2 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [Array,Array]=Fault", + "script": [ + "PUSH0", + "NEWARRAY", + "DUP", + "NUMNOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "NUMNOTEQUAL", + "evaluationStack": [ + { + "type": "array", + "value": [] + }, + { + "type": "array", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Array,Array]=fault", + "script": [ + "PUSH0", + "NEWARRAY", + "PUSH0", + "NEWARRAY", + "NUMNOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "NUMNOTEQUAL", + "evaluationStack": [ + { + "type": "array", + "value": [] + }, + { + "type": "array", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Struct,Struct]=fault with clone", + "script": [ + "PUSH0", + "NEWSTRUCT", + "DUP", + "NUMNOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "NUMNOTEQUAL", + "evaluationStack": [ + { + "type": "struct", + "value": [] + }, + { + "type": "struct", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Struct,Struct]=fault without clone", + "script": [ + "PUSH0", + "NEWSTRUCT", + "PUSH0", + "NEWSTRUCT", + "NUMNOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "NUMNOTEQUAL", + "evaluationStack": [ + { + "type": "struct", + "value": [] + }, + { + "type": "struct", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Struct,Struct]=fault", + "script": [ + "PUSH0", + "NEWSTRUCT", + "PUSH1", + "NEWSTRUCT", + "NUMNOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "NUMNOTEQUAL", + "evaluationStack": [ + { + "type": "struct", + "value": [ + { + "type": "Null" + } + ] + }, + { + "type": "struct", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Map,Map]=Fault", + "script": [ + "NEWMAP", + "DUP", + "NUMNOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "NUMNOTEQUAL", + "evaluationStack": [ + { + "type": "map", + "value": {} + }, + { + "type": "map", + "value": {} + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Map,Map]=Fault", + "script": [ + "NEWMAP", + "NEWMAP", + "NUMNOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "NUMNOTEQUAL", + "evaluationStack": [ + { + "type": "map", + "value": {} + }, + { + "type": "map", + "value": {} + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [ByteString,ByteString]=false", + "script": [ + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "NUMNOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 38, + "nextInstruction": "NUMNOTEQUAL", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + }, + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 39, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [ByteString,ByteString]=true same length", + "script": [ + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000124", + "NUMNOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 38, + "nextInstruction": "NUMNOTEQUAL", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000124" + }, + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 39, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [ByteString,ByteString]=true different length", + "script": [ + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "PUSHDATA1", + "0x10", + "0x00000000000000000000000000000124", + "NUMNOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 37, + "nextInstruction": "NUMNOTEQUAL", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x00000000000000000000000000000124" + }, + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 38, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [ByteString,ByteString]=true different length", + "script": [ + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "PUSHDATA1", + "0x10", + "0x00000000000000000000000000000124", + "NUMNOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 37, + "nextInstruction": "NUMNOTEQUAL", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x00000000000000000000000000000124" + }, + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 38, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test with clone [Interop,Interop]=fault", + "script": [ + "SYSCALL", + "0x77777777", + "DUP", + "NUMNOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "DUP", + "evaluationStack": [ + { + "type": "interop", + "value": "Object" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "NUMNOTEQUAL", + "evaluationStack": [ + { + "type": "interop", + "value": "Object" + }, + { + "type": "interop", + "value": "Object" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Interop,Interop]=fault", + "script": [ + "SYSCALL", + "0x77777777", + "SYSCALL", + "0x77777777", + "NUMNOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 10, + "nextInstruction": "NUMNOTEQUAL", + "evaluationStack": [ + { + "type": "interop", + "value": "Object" + }, + { + "type": "interop", + "value": "Object" + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/POW.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/POW.json new file mode 100644 index 0000000000..c9a100e2e4 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/POW.json @@ -0,0 +1,195 @@ +{ + "category": "Numeric", + "name": "POW", + "tests": [ + { + "name": "Exception - Without items", + "script": [ + "POW" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "POW", + "evaluationStack": [] + } + ] + } + } + ] + }, + { + "name": "Exception - With only one item", + "script": [ + "PUSH1", + "POW" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "FAULT", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "POW", + "evaluationStack": [] + } + ] + } + } + ] + }, + { + "name": "Real test 1", + "script": [ + "PUSH1", + "PUSH1", + "POW" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "invocationStack": [], + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + }, + { + "name": "Real test 1", + "script": [ + "PUSH9", + "PUSH2", + "POW" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": 81 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "invocationStack": [], + "resultStack": [ + { + "type": "Integer", + "value": 81 + } + ] + } + } + ] + }, + { + "name": "Real test 1", + "script": [ + "PUSH2", + "PUSH9", + "POW" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": 512 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "invocationStack": [], + "resultStack": [ + { + "type": "Integer", + "value": 512 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/SHL.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/SHL.json new file mode 100644 index 0000000000..7e660f6c92 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/SHL.json @@ -0,0 +1,245 @@ +{ + "category": "Numeric", + "name": "SHL", + "tests": [ + { + "name": "Exception - Above the limit 0 << 257", + "script": [ + "PUSH0", + "PUSHDATA1", + "0x02", + "0x0101", + "SHL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "SHL", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x0101" + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Exception - Below the limit 0 << -257", + "script": [ + "PUSH0", + "PUSHDATA1", + "0x02", + "0xfffe", + "SHL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "SHL", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0xFFFE" + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test 0 << 256", + "script": [ + "PUSH0", + "PUSHDATA1", + "0x02", + "0x0001", + "SHL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "SHL", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x0001" + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + } + ] + }, + { + "name": "Real test 16 << 4", + "script": [ + "PUSH16", + "PUSH4", + "SHL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "SHL", + "evaluationStack": [ + { + "type": "integer", + "value": 4 + }, + { + "type": "integer", + "value": 16 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 256 + } + ] + } + } + ] + }, + { + "name": "Real test 16 << 0", + "script": [ + "PUSH16", + "PUSH0", + "SHL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "SHL", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 16 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 16 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/SHR.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/SHR.json new file mode 100644 index 0000000000..818364e92a --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/SHR.json @@ -0,0 +1,247 @@ +{ + "category": "Numeric", + "name": "SHR", + "tests": [ + { + "name": "Exception - Above the limit 0 >> 257", + "script": [ + "PUSH0", + "PUSHDATA1", + "0x02", + "0x0101", + "SHR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "SHR", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x0101" + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Exception - Below the limit 0 >> -257", + "script": [ + "PUSH0", + "PUSHDATA1", + "0x02", + "0xfffe", + "SHR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "SHR", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0xFFFE" + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test 0 >> 256", + "script": [ + "PUSH0", + "PUSHDATA1", + "0x02", + "0x0001", + "SHR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "SHR", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x0001" + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + } + ] + }, + { + "name": "Real test 256 >> 0", + "script": [ + "PUSHDATA1", + "0x02", + "0x0001", + "PUSH0", + "SHR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "SHR", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "ByteString", + "value": "0x0001" + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "ByteString", + "value": "0x0001" + } + ] + } + } + ] + }, + { + "name": "Real test 4 >> 16", + "script": [ + "PUSH4", + "PUSH16", + "SHR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "SHR", + "evaluationStack": [ + { + "type": "integer", + "value": 16 + }, + { + "type": "integer", + "value": 4 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/SIGN.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/SIGN.json new file mode 100644 index 0000000000..24111873f9 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/SIGN.json @@ -0,0 +1,496 @@ +{ + "category": "Numeric", + "name": "SIGN", + "tests": [ + { + "name": "Without push", + "script": [ + "SIGN" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "With bool", + "script": [ + "PUSH1", + "NOT", + "SIGN" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "SIGN", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + } + ] + }, + { + "name": "With integer 0x03", + "script": [ + "PUSH3", + "SIGN" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "SIGN", + "evaluationStack": [ + { + "type": "integer", + "value": 3 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + } + ] + }, + { + "name": "With integer -1", + "script": [ + "PUSHM1", + "SIGN" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "SIGN", + "evaluationStack": [ + { + "type": "integer", + "value": -1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": -1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": -1 + } + ] + } + } + ] + }, + { + "name": "With map", + "script": [ + "NEWMAP", + "SIGN" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "SIGN", + "evaluationStack": [ + { + "type": "map", + "value": {} + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "With array", + "script": [ + "PUSH0", + "NEWARRAY", + "SIGN" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "SIGN", + "evaluationStack": [ + { + "type": "array", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "With struct", + "script": [ + "PUSH0", + "NEWSTRUCT", + "SIGN" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "SIGN", + "evaluationStack": [ + { + "type": "struct", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "With byte array 0x0000", + "script": [ + "PUSHDATA1", + "0x02", + "0x0000", + "SIGN" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "SIGN", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x0000" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + } + ] + }, + { + "name": "With byte array 0x0001", + "script": [ + "PUSHDATA1", + "0x02", + "0x0001", + "SIGN" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "SIGN", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x0001" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + } + ] + }, + { + "name": "With interop", + "script": [ + "SYSCALL", + "0x77777777", + "SIGN" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "SIGN", + "evaluationStack": [ + { + "type": "interop", + "value": "Object" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/SQRT.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/SQRT.json new file mode 100644 index 0000000000..8b63674edd --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/SQRT.json @@ -0,0 +1,216 @@ +{ + "category": "Numeric", + "name": "SQRT", + "tests": [ + { + "name": "Exception - Without items", + "script": [ + "SQRT" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "SQRT", + "evaluationStack": [] + } + ] + } + } + ] + }, + { + "name": "Real test 1", + "script": [ + "PUSH1", + "SQRT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "invocationStack": [], + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + }, + { + "name": "Real test with 81", + "script": [ + "PUSHINT8", + "0x51", + "SQRT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": 9 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "invocationStack": [], + "resultStack": [ + { + "type": "Integer", + "value": 9 + } + ] + } + } + ] + }, + { + "name": "Real test with 15625", + "script": [ + "PUSHINT16", + "0x093d", + "SQRT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": 125 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "invocationStack": [], + "resultStack": [ + { + "type": "Integer", + "value": 125 + } + ] + } + } + ] + }, + { + "name": "Real test with 4", + "script": [ + "PUSHINT8", + "0x04", + "SQRT" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "invocationStack": [], + "resultStack": [ + { + "type": "Integer", + "value": 2 + } + ] + } + } + ] + }, + { + "name": "Real test with 2", + "script": [ + "PUSHINT8", + "0x02", + "SQRT" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "invocationStack": [], + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/APPEND.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/APPEND.json new file mode 100644 index 0000000000..61593452e7 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/APPEND.json @@ -0,0 +1,679 @@ +{ + "category": "Arrays", + "name": "APPEND", + "tests": [ + { + "name": "Without push", + "script": [ + "PUSH1", + "APPEND" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without array", + "script": [ + "PUSH1", + "PUSH2", + "APPEND" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Clone test [Array]", + "script": [ + "INITSSLOT", + "0x01", + "PUSH0", + "NEWARRAY", + "DUP", + "PUSH5", + "APPEND", + "STSFLD0", + "PUSH0", + "NEWARRAY", + "DUP", + "LDSFLD0", + "APPEND", + "LDSFLD0", + "PUSH6", + "APPEND", + "LDSFLD0" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 8, + "nextInstruction": "PUSH0", + "staticFields": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 5 + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 10, + "nextInstruction": "DUP", + "evaluationStack": [ + { + "type": "array", + "value": [] + } + ], + "staticFields": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 5 + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 13, + "nextInstruction": "LDSFLD0", + "evaluationStack": [ + { + "type": "array", + "value": [ + { + "type": "Array", + "value": [ + { + "type": "Integer", + "value": "5" + } + ] + } + ] + } + ], + "staticFields": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": "5" + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 17, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": "5" + }, + { + "type": "Integer", + "value": "6" + } + ] + }, + { + "type": "array", + "value": [ + { + "type": "Array", + "value": [ + { + "type": "Integer", + "value": "5" + }, + { + "type": "Integer", + "value": "6" + } + ] + } + ] + } + ], + "staticFields": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": "5" + }, + { + "type": "Integer", + "value": "6" + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": "5" + }, + { + "type": "Integer", + "value": "6" + } + ] + }, + { + "type": "array", + "value": [ + { + "type": "Array", + "value": [ + { + "type": "Integer", + "value": "5" + }, + { + "type": "Integer", + "value": "6" + } + ] + } + ] + } + ] + } + } + ] + }, + { + "name": "Clone test [Struct]", + "script": [ + "INITSSLOT", + "0x01", + "PUSH0", + "NEWSTRUCT", + "DUP", + "PUSH5", + "APPEND", + "STSFLD0", + "PUSH0", + "NEWSTRUCT", + "DUP", + "LDSFLD0", + "APPEND", + "LDSFLD0", + "PUSH6", + "APPEND", + "LDSFLD0" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 8, + "nextInstruction": "PUSH0", + "staticFields": [ + { + "type": "struct", + "value": [ + { + "type": "Integer", + "value": 5 + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 10, + "nextInstruction": "DUP", + "evaluationStack": [ + { + "type": "struct", + "value": [] + } + ], + "staticFields": [ + { + "type": "struct", + "value": [ + { + "type": "Integer", + "value": 5 + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 13, + "nextInstruction": "LDSFLD0", + "evaluationStack": [ + { + "type": "struct", + "value": [ + { + "type": "Struct", + "value": [ + { + "type": "Integer", + "value": 5 + } + ] + } + ] + } + ], + "staticFields": [ + { + "type": "struct", + "value": [ + { + "type": "Integer", + "value": 5 + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 17, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "struct", + "value": [ + { + "type": "Integer", + "value": 5 + }, + { + "type": "Integer", + "value": 6 + } + ] + }, + { + "type": "struct", + "value": [ + { + "type": "Struct", + "value": [ + { + "type": "Integer", + "value": 5 + } + ] + } + ] + } + ], + "staticFields": [ + { + "type": "struct", + "value": [ + { + "type": "Integer", + "value": 5 + }, + { + "type": "Integer", + "value": 6 + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "struct", + "value": [ + { + "type": "Integer", + "value": 5 + }, + { + "type": "Integer", + "value": 6 + } + ] + }, + { + "type": "struct", + "value": [ + { + "type": "Struct", + "value": [ + { + "type": "Integer", + "value": 5 + } + ] + } + ] + } + ] + } + } + ] + }, + { + "name": "Real test [Array]", + "script": [ + "PUSH0", + "NEWARRAY", + "DUP", + "PUSH5", + "APPEND" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "APPEND", + "evaluationStack": [ + { + "type": "integer", + "value": 5 + }, + { + "type": "array", + "value": [] + }, + { + "type": "array", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": "5" + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": "5" + } + ] + } + ] + } + } + ] + }, + { + "name": "Real test [Struct]", + "script": [ + "PUSH0", + "NEWSTRUCT", + "DUP", + "PUSH5", + "APPEND" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "APPEND", + "evaluationStack": [ + { + "type": "integer", + "value": 5 + }, + { + "type": "struct", + "value": [] + }, + { + "type": "struct", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "struct", + "value": [ + { + "type": "Integer", + "value": "5" + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "struct", + "value": [ + { + "type": "Integer", + "value": "5" + } + ] + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/CLEARITEMS.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/CLEARITEMS.json new file mode 100644 index 0000000000..20f82d0a34 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/CLEARITEMS.json @@ -0,0 +1,126 @@ +{ + "category": "Arrays", + "name": "CLEARITEMS", + "tests": [ + { + "name": "Without push", + "script": [ + "CLEARITEMS" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Wrong type", + "script": [ + "PUSH2", + "CLEARITEMS" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Array]", + "script": [ + "PUSH0", + "PUSH1", + "PUSH2", + "PACK", + "DUP", + "CLEARITEMS" + ], + "steps": [ + { + "actions": [ + "StepInto", + "StepInto", + "StepInto", + "StepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "DUP", + "evaluationStack": [ + { + "type": "Array", + "value": [ + { + "type": "Integer", + "value": 1 + }, + { + "type": "Integer", + "value": 0 + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Array", + "value": [] + } + ] + } + } + ] + }, + { + "name": "Real test [Map]", + "script": [ + "NEWMAP", + "DUP", + "PUSH0", + "PUSH0", + "SETITEM", + "DUP", + "CLEARITEMS" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Map", + "value": {} + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/HASKEY.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/HASKEY.json new file mode 100644 index 0000000000..213087bd29 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/HASKEY.json @@ -0,0 +1,276 @@ +{ + "category": "Arrays", + "name": "HASKEY", + "tests": [ + { + "name": "Without push", + "script": [ + "HASKEY" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Wrong key type", + "script": [ + "PUSH1", + "NEWARRAY", + "NEWMAP", + "HASKEY" + ], + "steps": [ + { + "actions": [ + "StepInto", + "StepInto", + "StepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "HASKEY", + "evaluationStack": [ + { + "type": "Map", + "value": {} + }, + { + "type": "Array", + "value": [ + { + "type": "Null" + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Buffer]", + "script": [ + "PUSH2", + "NEWBUFFER", + "PUSH0", + "HASKEY" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [Map]", + "script": [ + "INITSSLOT", + "0x01", + "NEWMAP", + "DUP", + "STSFLD0", + "PUSH1", + "PUSH2", + "SETITEM", + "LDSFLD0", + "PUSH3", + "HASKEY", + "DROP", + "LDSFLD0", + "PUSH1", + "HASKEY" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 10, + "nextInstruction": "HASKEY", + "evaluationStack": [ + { + "type": "Integer", + "value": 3 + }, + { + "type": "map", + "value": { + "0x01": { + "type": "Integer", + "value": 2 + } + } + } + ], + "staticFields": [ + { + "type": "map", + "value": { + "0x01": { + "type": "Integer", + "value": 2 + } + } + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 11, + "nextInstruction": "DROP", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ], + "staticFields": [ + { + "type": "map", + "value": { + "0x01": { + "type": "Integer", + "value": 2 + } + } + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Check key size [Map]", + "script": [ + "NEWMAP", + "PUSHINT8", + "0x41", + "NEWBUFFER", + "CONVERT", + "0x28", + "HASKEY" + ], + "steps": [ + { + "actions": [ + "stepinto", + "stepinto", + "stepinto", + "stepinto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "HASKEY", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + }, + { + "type": "map", + "value": {} + } + ] + } + ] + } + }, + { + "actions": [ + "stepinto" + ], + "result": { + "state": "FAULT", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "HASKEY", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + }, + { + "type": "map", + "value": {} + } + ] + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/KEYS.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/KEYS.json new file mode 100644 index 0000000000..fb6a9d985c --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/KEYS.json @@ -0,0 +1,63 @@ +{ + "category": "Arrays", + "name": "KEYS", + "tests": [ + { + "name": "Keys in map", + "script": [ + "NEWMAP", + "DUP", + "DUP", + "PUSH0", + "PUSH0", + "SETITEM", + "PUSH1", + "PUSH1", + "SETITEM", + "KEYS" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 0 + }, + { + "type": "Integer", + "value": 1 + } + ] + } + ] + } + } + ] + }, + { + "name": "Invalid StackItem [Integer!=Map]", + "script": [ + "PUSH0", + "KEYS" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/NEWARRAY.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/NEWARRAY.json new file mode 100644 index 0000000000..5244c72f4e --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/NEWARRAY.json @@ -0,0 +1,168 @@ +{ + "category": "Arrays", + "name": "NEWARRAY", + "tests": [ + { + "name": "Without push", + "script": [ + "NEWARRAY" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "With wrong type", + "script": [ + "NEWMAP", + "NEWARRAY" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "With negative push", + "script": [ + "PUSHM1", + "NEWARRAY" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "PUSH2", + "NEWARRAY" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "array", + "value": [ + { + "type": "Null" + }, + { + "type": "Null" + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "array", + "value": [ + { + "type": "Null" + }, + { + "type": "Null" + } + ] + } + ] + } + } + ] + }, + { + "name": "Clone Struct to Array", + "script": [ + "PUSH1", + "NEWSTRUCT", + "DUP", + "PUSH0", + "PUSH5", + "SETITEM", + "NEWARRAY" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "NEWARRAY", + "evaluationStack": [ + { + "type": "struct", + "value": [ + { + "type": "Integer", + "value": 5 + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/NEWARRAY0.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/NEWARRAY0.json new file mode 100644 index 0000000000..21cd6436e2 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/NEWARRAY0.json @@ -0,0 +1,28 @@ +{ + "category": "Arrays", + "name": "NEWARRAY0", + "tests": [ + { + "name": "Real test", + "script": [ + "NEWARRAY0" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Array", + "value": [] + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/NEWARRAY_T.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/NEWARRAY_T.json new file mode 100644 index 0000000000..d1cea504ad --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/NEWARRAY_T.json @@ -0,0 +1,289 @@ +{ + "category": "Arrays", + "name": "NEWARRAY_T", + "tests": [ + { + "name": "Real test [Any]", + "script": [ + "PUSH2", + "NEWARRAY_T", + "0x00" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Array", + "value": [ + { + "type": "Null" + }, + { + "type": "Null" + } + ] + } + ] + } + } + ] + }, + { + "name": "Real test [Pointer]", + "script": [ + "PUSH2", + "NEWARRAY_T", + "0x10" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Array", + "value": [ + { + "type": "Null" + }, + { + "type": "Null" + } + ] + } + ] + } + } + ] + }, + { + "name": "Real test [Integer]", + "script": [ + "PUSH2", + "NEWARRAY_T", + "0x21" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Array", + "value": [ + { + "type": "Integer", + "value": 0 + }, + { + "type": "Integer", + "value": 0 + } + ] + } + ] + } + } + ] + }, + { + "name": "Real test [ByteString]", + "script": [ + "PUSH2", + "NEWARRAY_T", + "0x28" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Array", + "value": [ + { + "type": "ByteString", + "value": "0x" + }, + { + "type": "ByteString", + "value": "0x" + } + ] + } + ] + } + } + ] + }, + { + "name": "Real test [Buffer]", + "script": [ + "PUSH2", + "NEWARRAY_T", + "0x30" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Array", + "value": [ + { + "type": "Null" + }, + { + "type": "Null" + } + ] + } + ] + } + } + ] + }, + { + "name": "Real test [Array]", + "script": [ + "PUSH2", + "NEWARRAY_T", + "0x40" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Array", + "value": [ + { + "type": "Null" + }, + { + "type": "Null" + } + ] + } + ] + } + } + ] + }, + { + "name": "Real test [Struct]", + "script": [ + "PUSH2", + "NEWARRAY_T", + "0x41" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Array", + "value": [ + { + "type": "Null" + }, + { + "type": "Null" + } + ] + } + ] + } + } + ] + }, + { + "name": "Real test [Map]", + "script": [ + "PUSH2", + "NEWARRAY_T", + "0x48" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Array", + "value": [ + { + "type": "Null" + }, + { + "type": "Null" + } + ] + } + ] + } + } + ] + }, + { + "name": "Real test [InteropInterface]", + "script": [ + "PUSH2", + "NEWARRAY_T", + "0x60" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Array", + "value": [ + { + "type": "Null" + }, + { + "type": "Null" + } + ] + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/NEWMAP.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/NEWMAP.json new file mode 100644 index 0000000000..a7000735d5 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/NEWMAP.json @@ -0,0 +1,48 @@ +{ + "category": "Arrays", + "name": "NEWMAP", + "tests": [ + { + "name": "Real test", + "script": [ + "NEWMAP" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "map", + "value": {} + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "map", + "value": {} + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/NEWSTRUCT.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/NEWSTRUCT.json new file mode 100644 index 0000000000..dbe9547dde --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/NEWSTRUCT.json @@ -0,0 +1,168 @@ +{ + "category": "Arrays", + "name": "NEWSTRUCT", + "tests": [ + { + "name": "Without push", + "script": [ + "NEWSTRUCT" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "With wrong type", + "script": [ + "NEWMAP", + "NEWSTRUCT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "With negative push", + "script": [ + "PUSHM1", + "NEWSTRUCT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "PUSH2", + "NEWSTRUCT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "struct", + "value": [ + { + "type": "Null" + }, + { + "type": "Null" + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "struct", + "value": [ + { + "type": "Null" + }, + { + "type": "Null" + } + ] + } + ] + } + } + ] + }, + { + "name": "Clone Array to Struct", + "script": [ + "PUSH1", + "NEWARRAY", + "DUP", + "PUSH0", + "PUSH5", + "SETITEM", + "NEWSTRUCT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "NEWSTRUCT", + "evaluationStack": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 5 + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/NEWSTRUCT0.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/NEWSTRUCT0.json new file mode 100644 index 0000000000..62d5a595d3 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/NEWSTRUCT0.json @@ -0,0 +1,28 @@ +{ + "category": "Arrays", + "name": "NEWSTRUCT0", + "tests": [ + { + "name": "Real test", + "script": [ + "NEWSTRUCT0" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Struct", + "value": [] + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/PACK.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/PACK.json new file mode 100644 index 0000000000..e4cc0f40ee --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/PACK.json @@ -0,0 +1,127 @@ +{ + "category": "Arrays", + "name": "PACK", + "tests": [ + { + "name": "Real test", + "script": [ + "PUSH5", + "PUSH6", + "PUSH2", + "PACK" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 6 + }, + { + "type": "Integer", + "value": 5 + } + ] + } + ] + } + } + ] + }, + { + "name": "Not enough size", + "script": [ + "PUSH5", + "PUSH2", + "PACK" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Wrong type [Map]", + "script": [ + "NEWMAP", + "PACK" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Wrong type [Array]", + "script": [ + "PUSH1", + "NEWARRAY", + "PACK" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Wrong type [Struct]", + "script": [ + "PUSH1", + "NEWSTRUCT", + "PACK" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without items", + "script": [ + "PACK" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/PACKMAP.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/PACKMAP.json new file mode 100644 index 0000000000..dd68bd19c1 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/PACKMAP.json @@ -0,0 +1,127 @@ +{ + "category": "Arrays", + "name": "PACKMAP", + "tests": [ + { + "name": "Real test", + "script": [ + "PUSH5", + "PUSH6", + "PUSH1", + "PACKMAP" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Map", + "value": { + "0x06": { + "type": "Integer", + "value": "5" + } + } + } + ] + } + } + ] + }, + { + "name": "Not enough size", + "script": [ + "PUSH5", + "PUSH1", + "PACKMAP" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Wrong type [Map]", + "script": [ + "PUSH1", + "PUSH1", + "NEWMAP", + "PACKMAP" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Wrong type [Array]", + "script": [ + "PUSH1", + "PUSH1", + "NEWARRAY", + "PACKMAP" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Wrong type [Struct]", + "script": [ + "PUSH1", + "PUSH1", + "NEWSTRUCT", + "PACKMAP" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without items", + "script": [ + "PACKMAP" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/PACKSTRUCT.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/PACKSTRUCT.json new file mode 100644 index 0000000000..e0517d27b9 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/PACKSTRUCT.json @@ -0,0 +1,127 @@ +{ + "category": "Arrays", + "name": "PACK", + "tests": [ + { + "name": "Real test", + "script": [ + "PUSH5", + "PUSH6", + "PUSH2", + "PACKSTRUCT" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "struct", + "value": [ + { + "type": "Integer", + "value": 6 + }, + { + "type": "Integer", + "value": 5 + } + ] + } + ] + } + } + ] + }, + { + "name": "Not enough size", + "script": [ + "PUSH5", + "PUSH2", + "PACKSTRUCT" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Wrong type [Map]", + "script": [ + "NEWMAP", + "PACKSTRUCT" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Wrong type [Array]", + "script": [ + "PUSH1", + "NEWARRAY", + "PACKSTRUCT" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Wrong type [Struct]", + "script": [ + "PUSH1", + "NEWSTRUCT", + "PACKSTRUCT" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without items", + "script": [ + "PACKSTRUCT" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/PICKITEM.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/PICKITEM.json new file mode 100644 index 0000000000..19a6f54a8c --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/PICKITEM.json @@ -0,0 +1,714 @@ +{ + "category": "Arrays", + "name": "PICKITEM", + "tests": [ + { + "name": "Wrong array", + "script": [ + "PUSH0", + "PUSH0", + "PICKITEM" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without push", + "script": [ + "PICKITEM" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Wrong key type", + "script": [ + "PUSH1", + "NEWARRAY", + "NEWMAP", + "PICKITEM" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "PICKITEM", + "evaluationStack": [ + { + "type": "map", + "value": {} + }, + { + "type": "array", + "value": [ + { + "type": "Null" + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Out of bounds [Array]", + "script": [ + "PUSH2", + "NEWARRAY", + "PUSH3", + "PICKITEM" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "PICKITEM", + "evaluationStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "array", + "value": [ + { + "type": "Null" + }, + { + "type": "Null" + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Out of bounds [Struct]", + "script": [ + "PUSH2", + "NEWSTRUCT", + "PUSH3", + "PICKITEM" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "PICKITEM", + "evaluationStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "struct", + "value": [ + { + "type": "Null" + }, + { + "type": "Null" + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Array]", + "script": [ + "INITSSLOT", + "0x01", + "PUSH3", + "NEWARRAY", + "STSFLD0", + "LDSFLD0", + "PUSH0", + "PUSH1", + "SETITEM", + "LDSFLD0", + "PUSH1", + "PUSH2", + "SETITEM", + "LDSFLD0", + "PUSH2", + "PUSH3", + "SETITEM", + "LDSFLD0", + "PUSH2", + "PICKITEM" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 19, + "nextInstruction": "PICKITEM", + "evaluationStack": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 1 + }, + { + "type": "Integer", + "value": 2 + }, + { + "type": "Integer", + "value": 3 + } + ] + } + ], + "staticFields": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 1 + }, + { + "type": "Integer", + "value": 2 + }, + { + "type": "Integer", + "value": 3 + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 20, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 3 + } + ], + "staticFields": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 1 + }, + { + "type": "Integer", + "value": 2 + }, + { + "type": "Integer", + "value": 3 + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 3 + } + ] + } + } + ] + }, + { + "name": "Real test [Struct]", + "script": [ + "INITSSLOT", + "0x01", + "PUSH3", + "NEWSTRUCT", + "STSFLD0", + "LDSFLD0", + "PUSH0", + "PUSH1", + "SETITEM", + "LDSFLD0", + "PUSH1", + "PUSH2", + "SETITEM", + "LDSFLD0", + "PUSH2", + "PUSH3", + "SETITEM", + "LDSFLD0", + "PUSH2", + "PICKITEM" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 19, + "nextInstruction": "PICKITEM", + "evaluationStack": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "struct", + "value": [ + { + "type": "Integer", + "value": 1 + }, + { + "type": "Integer", + "value": 2 + }, + { + "type": "Integer", + "value": 3 + } + ] + } + ], + "staticFields": [ + { + "type": "struct", + "value": [ + { + "type": "Integer", + "value": 1 + }, + { + "type": "Integer", + "value": 2 + }, + { + "type": "Integer", + "value": 3 + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 20, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 3 + } + ], + "staticFields": [ + { + "type": "struct", + "value": [ + { + "type": "Integer", + "value": 1 + }, + { + "type": "Integer", + "value": 2 + }, + { + "type": "Integer", + "value": 3 + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 3 + } + ] + } + } + ] + }, + { + "name": "OutOfBounds with -1 [ByteString]", + "script": [ + "PUSHDATA1", + "0x02", + "0x0102", + "PUSHM1", + "PICKITEM" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "PICKITEM", + "evaluationStack": [ + { + "type": "integer", + "value": -1 + }, + { + "type": "ByteString", + "value": "0x0102" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "OutOfBounds with more than length [ByteString]", + "script": [ + "PUSHDATA1", + "0x02", + "0x0102", + "PUSH4", + "PICKITEM" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "PICKITEM", + "evaluationStack": [ + { + "type": "integer", + "value": 4 + }, + { + "type": "ByteString", + "value": "0x0102" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [ByteString]", + "script": [ + "PUSHDATA1", + "0x02", + "0x0102", + "PUSH0", + "PICKITEM" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "PICKITEM", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "ByteString", + "value": "0x0102" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + } + ] + }, + { + "name": "Real test [Buffer]", + "script": [ + "PUSHDATA1", + "0x02", + "0x0102", + "CONVERT", + "0x30", + "PUSH0", + "PICKITEM" + ], + "steps": [ + { + "actions": [ + "StepInto", + "StepInto", + "StepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 7, + "nextInstruction": "PICKITEM", + "evaluationStack": [ + { + "type": "Integer", + "value": 0 + }, + { + "type": "Buffer", + "value": "0x0102" + } + ] + } + ] + } + }, + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/REMOVE.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/REMOVE.json new file mode 100644 index 0000000000..cba3a4d0a7 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/REMOVE.json @@ -0,0 +1,299 @@ +{ + "category": "Arrays", + "name": "REMOVE", + "tests": [ + { + "name": "Without push", + "script": [ + "PUSH1", + "REMOVE" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without array", + "script": [ + "PUSH1", + "PUSH2", + "REMOVE" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Wrong key", + "script": [ + "PUSH2", + "NEWMAP", + "REMOVE" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Out of bounds", + "script": [ + "PUSH6", + "PUSH5", + "PUSH2", + "PACK", + "PUSH2", + "REMOVE" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Array]", + "script": [ + "INITSSLOT", + "0x01", + "PUSH6", + "PUSH5", + "PUSH2", + "PACK", + "STSFLD0", + "LDSFLD0", + "PUSH0", + "REMOVE", + "LDSFLD0", + "UNPACK" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 9, + "nextInstruction": "REMOVE", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 5 + }, + { + "type": "Integer", + "value": 6 + } + ] + } + ], + "staticFields": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 5 + }, + { + "type": "Integer", + "value": 6 + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 10, + "nextInstruction": "LDSFLD0", + "staticFields": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 6 + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 6 + } + ] + } + } + ] + }, + { + "name": "Real test [Struct]", + "script": [ + "INITSSLOT", + "0x01", + "PUSH0", + "NEWSTRUCT", + "DUP", + "PUSH5", + "APPEND", + "STSFLD0", + "LDSFLD0", + "PUSH0", + "REMOVE", + "LDSFLD0", + "UNPACK" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 10, + "nextInstruction": "REMOVE", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "struct", + "value": [ + { + "type": "Integer", + "value": 5 + } + ] + } + ], + "staticFields": [ + { + "type": "struct", + "value": [ + { + "type": "Integer", + "value": 5 + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 11, + "nextInstruction": "LDSFLD0", + "staticFields": [ + { + "type": "struct", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/REVERSEITEMS.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/REVERSEITEMS.json new file mode 100644 index 0000000000..b4dec58eaf --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/REVERSEITEMS.json @@ -0,0 +1,138 @@ +{ + "category": "Arrays", + "name": "REVERSEITEMS", + "tests": [ + { + "name": "Without push", + "script": [ + "REVERSEITEMS" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without Array", + "script": [ + "PUSH9", + "REVERSEITEMS" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Array]", + "script": [ + "PUSH9", + "PUSH8", + "PUSH2", + "PACK", + "DUP", + "REVERSEITEMS" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 9 + }, + { + "type": "Integer", + "value": 8 + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 9 + }, + { + "type": "Integer", + "value": 8 + } + ] + } + ] + } + } + ] + }, + { + "name": "Real test [Buffer]", + "script": [ + "PUSHDATA1", + "0x03", + "0x010203", + "CONVERT", + "0x30", + "DUP", + "REVERSEITEMS" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Buffer", + "value": "0x030201" + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/SETITEM.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/SETITEM.json new file mode 100644 index 0000000000..0ed33f2968 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/SETITEM.json @@ -0,0 +1,1226 @@ +{ + "category": "Arrays", + "name": "SETITEM", + "tests": [ + { + "name": "Map in key", + "script": [ + "PUSH1", + "NEWMAP", + "PUSH0", + "SETITEM" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "SETITEM", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "map", + "value": {} + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without array", + "script": [ + "PUSH0", + "PUSH0", + "PUSH0", + "SETITEM" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "SETITEM", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without push", + "script": [ + "SETITEM" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Out of bounds [Array]", + "script": [ + "PUSH1", + "NEWARRAY", + "PUSH1", + "PUSH5", + "SETITEM" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "SETITEM", + "evaluationStack": [ + { + "type": "integer", + "value": 5 + }, + { + "type": "integer", + "value": 1 + }, + { + "type": "array", + "value": [ + { + "type": "Null" + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Out of bounds [Struct]", + "script": [ + "PUSH1", + "NEWSTRUCT", + "PUSH1", + "PUSH5", + "SETITEM" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "SETITEM", + "evaluationStack": [ + { + "type": "integer", + "value": 5 + }, + { + "type": "integer", + "value": 1 + }, + { + "type": "struct", + "value": [ + { + "type": "Null" + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Map]", + "script": [ + "INITSSLOT", + "0x01", + "NEWMAP", + "DUP", + "STSFLD0", + "PUSH1", + "PUSH2", + "SETITEM", + "LDSFLD0", + "PUSH3", + "PUSH4", + "SETITEM", + "LDSFLD0", + "PUSH1", + "PUSH2", + "SETITEM", + "LDSFLD0", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "PUSH1", + "evaluationStack": [ + { + "type": "map", + "value": {} + } + ], + "staticFields": [ + { + "type": "map", + "value": {} + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 7, + "nextInstruction": "SETITEM", + "evaluationStack": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + }, + { + "type": "map", + "value": {} + } + ], + "staticFields": [ + { + "type": "map", + "value": {} + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 8, + "nextInstruction": "LDSFLD0", + "staticFields": [ + { + "type": "map", + "value": { + "0x01": { + "type": "Integer", + "value": 2 + } + } + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 9, + "nextInstruction": "PUSH3", + "evaluationStack": [ + { + "type": "map", + "value": { + "0x01": { + "type": "Integer", + "value": 2 + } + } + } + ], + "staticFields": [ + { + "type": "map", + "value": { + "0x01": { + "type": "Integer", + "value": 2 + } + } + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 13, + "nextInstruction": "PUSH1", + "evaluationStack": [ + { + "type": "map", + "value": { + "0x01": { + "type": "Integer", + "value": 2 + }, + "0x03": { + "type": "Integer", + "value": 4 + } + } + } + ], + "staticFields": [ + { + "type": "map", + "value": { + "0x01": { + "type": "Integer", + "value": 2 + }, + "0x03": { + "type": "Integer", + "value": 4 + } + } + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "map", + "value": { + "0x01": { + "type": "Integer", + "value": 2 + }, + "0x03": { + "type": "Integer", + "value": 4 + } + } + } + ] + } + } + ] + }, + { + "name": "Real test [Array]", + "script": [ + "PUSH1", + "NEWARRAY", + "DUP", + "PUSH0", + "PUSH5", + "SETITEM" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "SETITEM", + "evaluationStack": [ + { + "type": "integer", + "value": 5 + }, + { + "type": "integer", + "value": 0 + }, + { + "type": "array", + "value": [ + { + "type": "Null" + } + ] + }, + { + "type": "array", + "value": [ + { + "type": "Null" + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 5 + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 5 + } + ] + } + ] + } + } + ] + }, + { + "name": "Real test [Struct]", + "script": [ + "PUSH1", + "NEWSTRUCT", + "DUP", + "PUSH0", + "PUSH5", + "SETITEM" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "SETITEM", + "evaluationStack": [ + { + "type": "integer", + "value": 5 + }, + { + "type": "integer", + "value": 0 + }, + { + "type": "struct", + "value": [ + { + "type": "Null" + } + ] + }, + { + "type": "struct", + "value": [ + { + "type": "Null" + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "struct", + "value": [ + { + "type": "Integer", + "value": 5 + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "struct", + "value": [ + { + "type": "Integer", + "value": 5 + } + ] + } + ] + } + } + ] + }, + { + "name": "Clone test [Array]", + "script": [ + "INITSSLOT", + "0x01", + "PUSH1", + "NEWARRAY", + "DUP", + "PUSH0", + "PUSH5", + "SETITEM", + "STSFLD0", + "LDSFLD0", + "DUP", + "PUSH0", + "PUSH4", + "SETITEM", + "LDSFLD0" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "PUSH0", + "evaluationStack": [ + { + "type": "array", + "value": [ + { + "type": "Null" + } + ] + }, + { + "type": "array", + "value": [ + { + "type": "Null" + } + ] + } + ], + "staticFields": [ + { + "type": "Null" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 8, + "nextInstruction": "STSFLD0", + "evaluationStack": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 5 + } + ] + } + ], + "staticFields": [ + { + "type": "Null" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 11, + "nextInstruction": "PUSH0", + "evaluationStack": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 5 + } + ] + }, + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 5 + } + ] + } + ], + "staticFields": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 5 + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 14, + "nextInstruction": "LDSFLD0", + "evaluationStack": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 4 + } + ] + } + ], + "staticFields": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 4 + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 15, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 4 + } + ] + }, + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 4 + } + ] + } + ], + "staticFields": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 4 + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 4 + } + ] + }, + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 4 + } + ] + } + ] + } + } + ] + }, + { + "name": "Clone test [Struct]", + "script": [ + "INITSSLOT", + "0x01", + "PUSH1", + "NEWSTRUCT", + "DUP", + "PUSH0", + "PUSH5", + "SETITEM", + "STSFLD0", + "LDSFLD0", + "DUP", + "PUSH0", + "PUSH4", + "SETITEM", + "LDSFLD0" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "PUSH0", + "evaluationStack": [ + { + "type": "struct", + "value": [ + { + "type": "Null" + } + ] + }, + { + "type": "struct", + "value": [ + { + "type": "Null" + } + ] + } + ], + "staticFields": [ + { + "type": "Null" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 8, + "nextInstruction": "STSFLD0", + "evaluationStack": [ + { + "type": "struct", + "value": [ + { + "type": "Integer", + "value": 5 + } + ] + } + ], + "staticFields": [ + { + "type": "Null" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 11, + "nextInstruction": "PUSH0", + "evaluationStack": [ + { + "type": "struct", + "value": [ + { + "type": "Integer", + "value": 5 + } + ] + }, + { + "type": "struct", + "value": [ + { + "type": "Integer", + "value": 5 + } + ] + } + ], + "staticFields": [ + { + "type": "struct", + "value": [ + { + "type": "Integer", + "value": 5 + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 14, + "nextInstruction": "LDSFLD0", + "evaluationStack": [ + { + "type": "struct", + "value": [ + { + "type": "Integer", + "value": 4 + } + ] + } + ], + "staticFields": [ + { + "type": "struct", + "value": [ + { + "type": "Integer", + "value": 4 + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 15, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "struct", + "value": [ + { + "type": "Integer", + "value": 4 + } + ] + }, + { + "type": "struct", + "value": [ + { + "type": "Integer", + "value": 4 + } + ] + } + ], + "staticFields": [ + { + "type": "struct", + "value": [ + { + "type": "Integer", + "value": 4 + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "struct", + "value": [ + { + "type": "Integer", + "value": 4 + } + ] + }, + { + "type": "struct", + "value": [ + { + "type": "Integer", + "value": 4 + } + ] + } + ] + } + } + ] + }, + { + "name": "Real test [Buffer]", + "script": [ + "PUSHDATA1", + "0x02", + "0x0102", + "CONVERT", + "0x30", + "DUP", + "PUSH0", + "PUSH5", + "SETITEM" + ], + "steps": [ + { + "actions": [ + "StepInto", + "StepInto", + "StepInto", + "StepInto", + "StepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 9, + "nextInstruction": "SETITEM", + "evaluationStack": [ + { + "type": "Integer", + "value": 5 + }, + { + "type": "Integer", + "value": 0 + }, + { + "type": "Buffer", + "value": "0x0102" + }, + { + "type": "Buffer", + "value": "0x0102" + } + ] + } + ] + } + }, + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Buffer", + "value": "0x0502" + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/SIZE.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/SIZE.json new file mode 100644 index 0000000000..fa2907411d --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/SIZE.json @@ -0,0 +1,266 @@ +{ + "category": "Arrays", + "name": "SIZE", + "tests": [ + { + "name": "Wrong type SYSCALL[0x77777777]+SIZE", + "script": [ + "SYSCALL", + "0x77777777", + "SIZE" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "SIZE", + "evaluationStack": [ + { + "type": "interop", + "value": "Object" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without PUSH+SIZE", + "script": [ + "SIZE" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test with ByteString", + "script": [ + "PUSHDATA1", + "0x01", + "0x00", + "SIZE" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + } + ] + }, + { + "name": "Real test with Buffer", + "script": [ + "PUSH10", + "NEWBUFFER", + "SIZE" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 10 + } + ] + } + } + ] + }, + { + "name": "Real test with array", + "script": [ + "PUSH3", + "NEWARRAY", + "SIZE" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 3 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 3 + } + ] + } + } + ] + }, + { + "name": "Real test with struct", + "script": [ + "PUSH3", + "NEWSTRUCT", + "SIZE" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 3 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 3 + } + ] + } + } + ] + }, + { + "name": "Real test with map", + "script": [ + "NEWMAP", + "SIZE" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/UNPACK.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/UNPACK.json new file mode 100644 index 0000000000..63c92aad3a --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/UNPACK.json @@ -0,0 +1,168 @@ +{ + "category": "Arrays", + "name": "UNPACK", + "tests": [ + { + "name": "Without array", + "script": [ + "PUSH10", + "UNPACK" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without push", + "script": [ + "UNPACK" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without push", + "script": [ + "PUSH5", + "PUSH6", + "PUSH2", + "PACK", + "UNPACK" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "UNPACK", + "evaluationStack": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 6 + }, + { + "type": "Integer", + "value": 5 + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 6 + }, + { + "type": "integer", + "value": 5 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 6 + }, + { + "type": "integer", + "value": 5 + } + ] + } + } + ] + }, + { + "name": "With map", + "script": [ + "PUSH5", + "PUSH6", + "PUSH1", + "PACKMAP", + "UNPACK" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + }, + { + "type": "Integer", + "value": 6 + }, + { + "type": "Integer", + "value": 5 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/VALUES.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/VALUES.json new file mode 100644 index 0000000000..4cf832eb59 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arrays/VALUES.json @@ -0,0 +1,194 @@ +{ + "category": "Arrays", + "name": "VALUES", + "tests": [ + { + "name": "No StackItem", + "script": [ + "KEYS" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Invalid StackItem [Integer != (Map|Array|Struct)]", + "script": [ + "PUSH0", + "KEYS" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Values in map", + "script": [ + "NEWMAP", + "DUP", + "DUP", + "PUSH0", + "PUSH2", + "SETITEM", + "PUSH1", + "PUSH4", + "SETITEM", + "VALUES" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 2 + }, + { + "type": "Integer", + "value": 4 + } + ] + } + ] + } + } + ] + }, + { + "name": "Simple values in array", + "script": [ + "PUSH2", + "NEWARRAY_T", + "0x00", + "VALUES" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "array", + "value": [ + { + "type": "Null" + }, + { + "type": "Null" + } + ] + } + ] + } + } + ] + }, + { + "name": "Compound value in array [inner struct]", + "script": [ + "PUSH1", + "NEWARRAY", + "DUP", + "PUSH0", + "PUSH2", + "NEWSTRUCT", + "SETITEM", + "VALUES" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "array", + "value": [ + { + "type": "struct", + "value": [ + { + "type": "Null" + }, + { + "type": "Null" + } + ] + } + ] + } + ] + } + } + ] + }, + { + "name": "Compound value in array [inner map]", + "script": [ + "PUSH1", + "NEWARRAY", + "DUP", + "PUSH0", + "NEWMAP", + "DUP", + "PUSH0", + "PUSH1", + "SETITEM", + "SETITEM", + "VALUES" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "array", + "value": [ + { + "type": "map", + "value": { + "": { + "type": "Integer", + "value": 1 + } + } + } + ] + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/BitwiseLogic/AND.json b/tests/Neo.VM.Tests/Tests/OpCodes/BitwiseLogic/AND.json new file mode 100644 index 0000000000..f1e1cc7d14 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/BitwiseLogic/AND.json @@ -0,0 +1,811 @@ +{ + "category": "Bitwise Logic", + "name": "AND", + "tests": [ + { + "name": "Without two pushes", + "script": [ + "AND" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without one push", + "script": [ + "PUSH0", + "AND" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "AND", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [true,true]=1", + "script": [ + "PUSH0", + "NOT", + "PUSH0", + "NOT", + "AND" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "AND", + "evaluationStack": [ + { + "type": "boolean", + "value": true + }, + { + "type": "boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + }, + { + "name": "Real test [false,true]=0", + "script": [ + "PUSH0", + "NOT", + "PUSH0", + "NOT", + "NOT", + "AND" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "AND", + "evaluationStack": [ + { + "type": "boolean", + "value": false + }, + { + "type": "boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 0 + } + ] + } + } + ] + }, + { + "name": "Real test [1,1]=1", + "script": [ + "PUSH1", + "PUSH1", + "AND" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "AND", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + }, + { + "name": "Real test [1,2]=0", + "script": [ + "PUSH2", + "PUSH1", + "AND" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "AND", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 2 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 0 + } + ] + } + } + ] + }, + { + "name": "Real test [Array,Array]=FAULT", + "script": [ + "PUSH0", + "NEWARRAY", + "DUP", + "AND" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "AND", + "evaluationStack": [ + { + "type": "array", + "value": [] + }, + { + "type": "array", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Struct,Struct]=FAULT", + "script": [ + "PUSH0", + "NEWSTRUCT", + "DUP", + "AND" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "AND", + "evaluationStack": [ + { + "type": "struct", + "value": [] + }, + { + "type": "struct", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Map,Map]=FAULT", + "script": [ + "NEWMAP", + "DUP", + "AND" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "AND", + "evaluationStack": [ + { + "type": "map", + "value": {} + }, + { + "type": "map", + "value": {} + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [ByteString,ByteString]=11911212070228631137091015067172167745536", + "script": [ + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "AND" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 38, + "nextInstruction": "AND", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + }, + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 39, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": "11911212070228631137091015067172167745536" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": "11911212070228631137091015067172167745536" + } + ] + } + } + ] + }, + { + "name": "Real test [ByteString,ByteString]=10890364969465815746700891244876863111168", + "script": [ + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000124", + "AND" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 38, + "nextInstruction": "AND", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000124" + }, + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 39, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": "10890364969465815746700891244876863111168" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": "10890364969465815746700891244876863111168" + } + ] + } + } + ] + }, + { + "name": "Real test [ByteString,ByteString]=0", + "script": [ + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "PUSHDATA1", + "0x10", + "0x00000000000000000000000000000124", + "AND" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 37, + "nextInstruction": "AND", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x00000000000000000000000000000124" + }, + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 38, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 0 + } + ] + } + } + ] + }, + { + "name": "Real test with clone [Interop,Interop]=FAULT", + "script": [ + "SYSCALL", + "0x77777777", + "DUP", + "AND" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "DUP", + "evaluationStack": [ + { + "type": "interop", + "value": "Object" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "AND", + "evaluationStack": [ + { + "type": "interop", + "value": "Object" + }, + { + "type": "interop", + "value": "Object" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Buffer,Buffer]=FAULT", + "script": [ + "PUSHDATA1", + "0x01AA", + "CONVERT", + "0x30", + "PUSHDATA1", + "0x01BB", + "CONVERT", + "0x30", + "AND" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 10, + "nextInstruction": "AND", + "evaluationStack": [ + { + "type": "Buffer", + "value": "0xBB" + }, + { + "type": "Buffer", + "value": "0xAA" + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/BitwiseLogic/EQUAL.json b/tests/Neo.VM.Tests/Tests/OpCodes/BitwiseLogic/EQUAL.json new file mode 100644 index 0000000000..75a484d0db --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/BitwiseLogic/EQUAL.json @@ -0,0 +1,1447 @@ +{ + "category": "Bitwise Logic", + "name": "EQUAL same types", + "tests": [ + { + "name": "Without two pushes", + "script": [ + "EQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without one push", + "script": [ + "PUSH0", + "EQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "EQUAL", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Bool,Bool]=true", + "script": [ + "PUSH0", + "NOT", + "PUSH0", + "NOT", + "EQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "EQUAL", + "evaluationStack": [ + { + "type": "boolean", + "value": true + }, + { + "type": "boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [Bool,Bool]=false", + "script": [ + "PUSH0", + "NOT", + "PUSH0", + "NOT", + "NOT", + "EQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "EQUAL", + "evaluationStack": [ + { + "type": "boolean", + "value": false + }, + { + "type": "boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [Integer,Integer]=true", + "script": [ + "PUSH1", + "PUSH1", + "EQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "EQUAL", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [Integer,Integer]=false", + "script": [ + "PUSH2", + "PUSH1", + "EQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "EQUAL", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 2 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [Array,Array]=true", + "script": [ + "PUSH0", + "NEWARRAY", + "DUP", + "EQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "EQUAL", + "evaluationStack": [ + { + "type": "array", + "value": [] + }, + { + "type": "array", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [Array,Array]=false", + "script": [ + "PUSH0", + "NEWARRAY", + "PUSH0", + "NEWARRAY", + "EQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "EQUAL", + "evaluationStack": [ + { + "type": "array", + "value": [] + }, + { + "type": "array", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [Struct,Struct]=true with clone", + "script": [ + "PUSH0", + "NEWSTRUCT", + "DUP", + "EQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "EQUAL", + "evaluationStack": [ + { + "type": "struct", + "value": [] + }, + { + "type": "struct", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [Struct,Struct]=true without clone", + "script": [ + "PUSH0", + "NEWSTRUCT", + "PUSH0", + "NEWSTRUCT", + "EQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "EQUAL", + "evaluationStack": [ + { + "type": "struct", + "value": [] + }, + { + "type": "struct", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [Struct,Struct]=false", + "script": [ + "PUSH0", + "NEWSTRUCT", + "PUSH1", + "NEWSTRUCT", + "EQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "EQUAL", + "evaluationStack": [ + { + "type": "struct", + "value": [ + { + "type": "Null" + } + ] + }, + { + "type": "struct", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [Map,Map]=true", + "script": [ + "NEWMAP", + "DUP", + "EQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "EQUAL", + "evaluationStack": [ + { + "type": "map", + "value": {} + }, + { + "type": "map", + "value": {} + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [Map,Map]=false", + "script": [ + "NEWMAP", + "NEWMAP", + "EQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "EQUAL", + "evaluationStack": [ + { + "type": "map", + "value": {} + }, + { + "type": "map", + "value": {} + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [ByteString,ByteString]=true", + "script": [ + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "EQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 38, + "nextInstruction": "EQUAL", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + }, + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 39, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [ByteString,ByteString]=false same length", + "script": [ + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000124", + "EQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 38, + "nextInstruction": "EQUAL", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000124" + }, + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 39, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [ByteString,ByteString]=false different length", + "script": [ + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "PUSHDATA1", + "0x10", + "0x00000000000000000000000000000124", + "EQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 37, + "nextInstruction": "EQUAL", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x00000000000000000000000000000124" + }, + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 38, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [ByteString,ByteString]=false different length", + "script": [ + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "PUSHDATA1", + "0x10", + "0x00000000000000000000000000000124", + "EQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 37, + "nextInstruction": "EQUAL", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x00000000000000000000000000000124" + }, + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 38, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test with clone [Interop,Interop]=true", + "script": [ + "SYSCALL", + "0x77777777", + "DUP", + "EQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "DUP", + "evaluationStack": [ + { + "type": "interop", + "value": "Object" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "EQUAL", + "evaluationStack": [ + { + "type": "interop", + "value": "Object" + }, + { + "type": "interop", + "value": "Object" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 7, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [Interop,Interop]=false", + "script": [ + "SYSCALL", + "0x77777777", + "SYSCALL", + "0x77777777", + "EQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 10, + "nextInstruction": "EQUAL", + "evaluationStack": [ + { + "type": "interop", + "value": "Object" + }, + { + "type": "interop", + "value": "Object" + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [Buffer,Buffer]=false", + "script": [ + "PUSHDATA1", + "0x01AA", + "CONVERT", + "0x30", + "PUSHDATA1", + "0x01BB", + "CONVERT", + "0x30", + "EQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 10, + "nextInstruction": "EQUAL", + "evaluationStack": [ + { + "type": "Buffer", + "value": "0xBB" + }, + { + "type": "Buffer", + "value": "0xAA" + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [Buffer,Buffer]=false", + "script": [ + "PUSHDATA1", + "0x01AA", + "CONVERT", + "0x30", + "PUSHDATA1", + "0x01AA", + "CONVERT", + "0x30", + "EQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 10, + "nextInstruction": "EQUAL", + "evaluationStack": [ + { + "type": "Buffer", + "value": "0xAA" + }, + { + "type": "Buffer", + "value": "0xAA" + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [Buffer,Buffer]=true", + "script": [ + "PUSHDATA1", + "0x01AA", + "CONVERT", + "0x30", + "DUP", + "EQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "EQUAL", + "evaluationStack": [ + { + "type": "Buffer", + "value": "0xAA" + }, + { + "type": "Buffer", + "value": "0xAA" + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/BitwiseLogic/INVERT.json b/tests/Neo.VM.Tests/Tests/OpCodes/BitwiseLogic/INVERT.json new file mode 100644 index 0000000000..636e08628b --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/BitwiseLogic/INVERT.json @@ -0,0 +1,657 @@ +{ + "category": "Bitwise Logic", + "name": "INVERT", + "tests": [ + { + "name": "Without two pushes", + "script": [ + "INVERT" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "With 0", + "script": [ + "PUSH0", + "INVERT" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "INVERT", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": -1 + } + ] + } + } + ] + }, + { + "name": "Real test [true]=-2", + "script": [ + "PUSH0", + "NOT", + "INVERT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "INVERT", + "evaluationStack": [ + { + "type": "boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": -2 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": -2 + } + ] + } + } + ] + }, + { + "name": "Real test [false]=-1", + "script": [ + "PUSH0", + "NOT", + "NOT", + "INVERT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "INVERT", + "evaluationStack": [ + { + "type": "boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": -1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": -1 + } + ] + } + } + ] + }, + { + "name": "Real test [1]=-2", + "script": [ + "PUSH1", + "INVERT" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "INVERT", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": -2 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": -2 + } + ] + } + } + ] + }, + { + "name": "Real test [2]=-3", + "script": [ + "PUSH2", + "INVERT" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "INVERT", + "evaluationStack": [ + { + "type": "integer", + "value": 2 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": -3 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": -3 + } + ] + } + } + ] + }, + { + "name": "Real test [Array]=FAULT", + "script": [ + "PUSH0", + "NEWARRAY", + "INVERT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "INVERT", + "evaluationStack": [ + { + "type": "array", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Struct]=FAULT", + "script": [ + "PUSH0", + "NEWSTRUCT", + "INVERT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "INVERT", + "evaluationStack": [ + { + "type": "struct", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Map]=FAULT", + "script": [ + "NEWMAP", + "INVERT" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "INVERT", + "evaluationStack": [ + { + "type": "map", + "value": {} + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [ByteString]=-11911212070228631137091015067172167745537", + "script": [ + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "INVERT" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 19, + "nextInstruction": "INVERT", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 20, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": "-11911212070228631137091015067172167745537" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": "-11911212070228631137091015067172167745537" + } + ] + } + } + ] + }, + { + "name": "Real test [ByteString]=-12251494437149569600554389674603935956993", + "script": [ + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000124", + "INVERT" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 19, + "nextInstruction": "INVERT", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000124" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 20, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": "-12251494437149569600554389674603935956993" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": "-12251494437149569600554389674603935956993" + } + ] + } + } + ] + }, + { + "name": "Real test with clone [Interop]=FAULT", + "script": [ + "SYSCALL", + "0x77777777", + "INVERT" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "INVERT", + "evaluationStack": [ + { + "type": "interop", + "value": "Object" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Buffer,Buffer]=FAULT", + "script": [ + "PUSHDATA1", + "0x01AA", + "CONVERT", + "0x30", + "PUSHDATA1", + "0x01BB", + "CONVERT", + "0x30", + "INVERT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 10, + "nextInstruction": "INVERT", + "evaluationStack": [ + { + "type": "Buffer", + "value": "0xBB" + }, + { + "type": "Buffer", + "value": "0xAA" + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/BitwiseLogic/NOTEQUAL.json b/tests/Neo.VM.Tests/Tests/OpCodes/BitwiseLogic/NOTEQUAL.json new file mode 100644 index 0000000000..95acf9509d --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/BitwiseLogic/NOTEQUAL.json @@ -0,0 +1,1299 @@ +{ + "category": "Bitwise Logic", + "name": "NOTEQUAL", + "tests": [ + { + "name": "Without two pushes", + "script": [ + "NOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without one push", + "script": [ + "PUSH0", + "NOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "NOTEQUAL", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "StepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Integer,Integer]=false", + "script": [ + "PUSH1", + "PUSH1", + "NOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "NOTEQUAL", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [Integer,Integer]=true", + "script": [ + "PUSH2", + "PUSH1", + "NOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "NOTEQUAL", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 2 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [Array,Array]=false", + "script": [ + "PUSH0", + "NEWARRAY", + "DUP", + "NOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "NOTEQUAL", + "evaluationStack": [ + { + "type": "array", + "value": [] + }, + { + "type": "array", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [Array,Array]=true", + "script": [ + "PUSH0", + "NEWARRAY", + "PUSH0", + "NEWARRAY", + "NOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "NOTEQUAL", + "evaluationStack": [ + { + "type": "array", + "value": [] + }, + { + "type": "array", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [Struct,Struct]=false with clone", + "script": [ + "PUSH0", + "NEWSTRUCT", + "DUP", + "NOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "NOTEQUAL", + "evaluationStack": [ + { + "type": "struct", + "value": [] + }, + { + "type": "struct", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [Struct,Struct]=false without clone", + "script": [ + "PUSH0", + "NEWSTRUCT", + "PUSH0", + "NEWSTRUCT", + "NOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "NOTEQUAL", + "evaluationStack": [ + { + "type": "struct", + "value": [] + }, + { + "type": "struct", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [Struct,Struct]=true", + "script": [ + "PUSH0", + "NEWSTRUCT", + "PUSH1", + "NEWSTRUCT", + "NOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "NOTEQUAL", + "evaluationStack": [ + { + "type": "struct", + "value": [ + { + "type": "Null" + } + ] + }, + { + "type": "struct", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [Map,Map]=false", + "script": [ + "NEWMAP", + "DUP", + "NOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "NOTEQUAL", + "evaluationStack": [ + { + "type": "map", + "value": {} + }, + { + "type": "map", + "value": {} + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [Map,Map]=true", + "script": [ + "NEWMAP", + "NEWMAP", + "NOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "NOTEQUAL", + "evaluationStack": [ + { + "type": "map", + "value": {} + }, + { + "type": "map", + "value": {} + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [ByteString,ByteString]=false", + "script": [ + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "NOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 38, + "nextInstruction": "NOTEQUAL", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + }, + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 39, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [ByteString,ByteString]=true same length", + "script": [ + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000124", + "NOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 38, + "nextInstruction": "NOTEQUAL", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000124" + }, + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 39, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [ByteString,ByteString]=true different length", + "script": [ + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "PUSHDATA1", + "0x10", + "0x00000000000000000000000000000124", + "NOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 37, + "nextInstruction": "NOTEQUAL", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x00000000000000000000000000000124" + }, + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 38, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [ByteString,ByteString]=true different length", + "script": [ + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "PUSHDATA1", + "0x10", + "0x00000000000000000000000000000124", + "NOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 37, + "nextInstruction": "NOTEQUAL", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x00000000000000000000000000000124" + }, + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 38, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test with clone [Interop,Interop]=false", + "script": [ + "SYSCALL", + "0x77777777", + "DUP", + "NOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "DUP", + "evaluationStack": [ + { + "type": "interop", + "value": "Object" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "NOTEQUAL", + "evaluationStack": [ + { + "type": "interop", + "value": "Object" + }, + { + "type": "interop", + "value": "Object" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 7, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Real test [Interop,Interop]=true", + "script": [ + "SYSCALL", + "0x77777777", + "SYSCALL", + "0x77777777", + "NOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 10, + "nextInstruction": "NOTEQUAL", + "evaluationStack": [ + { + "type": "interop", + "value": "Object" + }, + { + "type": "interop", + "value": "Object" + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [Buffer,Buffer]=true", + "script": [ + "PUSHDATA1", + "0x01AA", + "CONVERT", + "0x30", + "PUSHDATA1", + "0x01BB", + "CONVERT", + "0x30", + "NOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 10, + "nextInstruction": "NOTEQUAL", + "evaluationStack": [ + { + "type": "Buffer", + "value": "0xBB" + }, + { + "type": "Buffer", + "value": "0xAA" + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [Buffer,Buffer]=true", + "script": [ + "PUSHDATA1", + "0x01AA", + "CONVERT", + "0x30", + "PUSHDATA1", + "0x01AA", + "CONVERT", + "0x30", + "NOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 10, + "nextInstruction": "NOTEQUAL", + "evaluationStack": [ + { + "type": "Buffer", + "value": "0xAA" + }, + { + "type": "Buffer", + "value": "0xAA" + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Real test [Buffer,Buffer]=false", + "script": [ + "PUSHDATA1", + "0x01AA", + "CONVERT", + "0x30", + "DUP", + "NOTEQUAL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "NOTEQUAL", + "evaluationStack": [ + { + "type": "Buffer", + "value": "0xAA" + }, + { + "type": "Buffer", + "value": "0xAA" + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/BitwiseLogic/OR.json b/tests/Neo.VM.Tests/Tests/OpCodes/BitwiseLogic/OR.json new file mode 100644 index 0000000000..fb52b32b71 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/BitwiseLogic/OR.json @@ -0,0 +1,811 @@ +{ + "category": "Bitwise Logic", + "name": "OR", + "tests": [ + { + "name": "Without two pushes", + "script": [ + "OR" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without one push", + "script": [ + "PUSH0", + "OR" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "OR", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [true,true]=1", + "script": [ + "PUSH0", + "NOT", + "PUSH0", + "NOT", + "OR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "OR", + "evaluationStack": [ + { + "type": "boolean", + "value": true + }, + { + "type": "boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + }, + { + "name": "Real test [false,true]=1", + "script": [ + "PUSH0", + "NOT", + "PUSH0", + "NOT", + "NOT", + "OR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "OR", + "evaluationStack": [ + { + "type": "boolean", + "value": false + }, + { + "type": "boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + }, + { + "name": "Real test [1,1]=1", + "script": [ + "PUSH1", + "PUSH1", + "OR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "OR", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + }, + { + "name": "Real test [1,2]=3", + "script": [ + "PUSH2", + "PUSH1", + "OR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "OR", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 2 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": 3 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 3 + } + ] + } + } + ] + }, + { + "name": "Real test [Array,Array]=FAULT", + "script": [ + "PUSH0", + "NEWARRAY", + "DUP", + "OR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "OR", + "evaluationStack": [ + { + "type": "array", + "value": [] + }, + { + "type": "array", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Struct,Struct]=FAULT", + "script": [ + "PUSH0", + "NEWSTRUCT", + "DUP", + "OR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "OR", + "evaluationStack": [ + { + "type": "struct", + "value": [] + }, + { + "type": "struct", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Map,Map]=FAULT", + "script": [ + "NEWMAP", + "DUP", + "OR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "OR", + "evaluationStack": [ + { + "type": "map", + "value": {} + }, + { + "type": "map", + "value": {} + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [ByteString,ByteString]=11911212070228631137091015067172167745536", + "script": [ + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "OR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 38, + "nextInstruction": "OR", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + }, + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 39, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": "11911212070228631137091015067172167745536" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": "11911212070228631137091015067172167745536" + } + ] + } + } + ] + }, + { + "name": "Real test [ByteString,ByteString]=13272341537912384990944513496899240591360", + "script": [ + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000124", + "OR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 38, + "nextInstruction": "OR", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000124" + }, + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 39, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": "13272341537912384990944513496899240591360" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": "13272341537912384990944513496899240591360" + } + ] + } + } + ] + }, + { + "name": "Real test [ByteString,ByteString]=11959069470373746643343180651838589370368", + "script": [ + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "PUSHDATA1", + "0x10", + "0x00000000000000000000000000000124", + "OR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 37, + "nextInstruction": "OR", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x00000000000000000000000000000124" + }, + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 38, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": "11959069470373746643343180651838589370368" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": "11959069470373746643343180651838589370368" + } + ] + } + } + ] + }, + { + "name": "Real test with clone [Interop,Interop]=FAULT", + "script": [ + "SYSCALL", + "0x77777777", + "DUP", + "OR" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "DUP", + "evaluationStack": [ + { + "type": "interop", + "value": "Object" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "OR", + "evaluationStack": [ + { + "type": "interop", + "value": "Object" + }, + { + "type": "interop", + "value": "Object" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Buffer,Buffer]=FAULT", + "script": [ + "PUSHDATA1", + "0x01AA", + "CONVERT", + "0x30", + "PUSHDATA1", + "0x01BB", + "CONVERT", + "0x30", + "OR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 10, + "nextInstruction": "OR", + "evaluationStack": [ + { + "type": "Buffer", + "value": "0xBB" + }, + { + "type": "Buffer", + "value": "0xAA" + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/BitwiseLogic/XOR.json b/tests/Neo.VM.Tests/Tests/OpCodes/BitwiseLogic/XOR.json new file mode 100644 index 0000000000..bae4cf557b --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/BitwiseLogic/XOR.json @@ -0,0 +1,811 @@ +{ + "category": "Bitwise Logic", + "name": "XOR", + "tests": [ + { + "name": "Without two pushes", + "script": [ + "XOR" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without one push", + "script": [ + "PUSH0", + "XOR" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "XOR", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [true,true]=0", + "script": [ + "PUSH0", + "NOT", + "PUSH0", + "NOT", + "XOR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "XOR", + "evaluationStack": [ + { + "type": "boolean", + "value": true + }, + { + "type": "boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 0 + } + ] + } + } + ] + }, + { + "name": "Real test [false,true]=1", + "script": [ + "PUSH0", + "NOT", + "PUSH0", + "NOT", + "NOT", + "XOR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "XOR", + "evaluationStack": [ + { + "type": "boolean", + "value": false + }, + { + "type": "boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + }, + { + "name": "Real test [1,1]=0", + "script": [ + "PUSH1", + "PUSH1", + "XOR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "XOR", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 0 + } + ] + } + } + ] + }, + { + "name": "Real test [1,2]=3", + "script": [ + "PUSH2", + "PUSH1", + "XOR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "XOR", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 2 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": 3 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 3 + } + ] + } + } + ] + }, + { + "name": "Real test [Array,Array]=FAULT", + "script": [ + "PUSH0", + "NEWARRAY", + "DUP", + "XOR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "XOR", + "evaluationStack": [ + { + "type": "array", + "value": [] + }, + { + "type": "array", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Struct,Struct]=FAULT", + "script": [ + "PUSH0", + "NEWSTRUCT", + "DUP", + "XOR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "XOR", + "evaluationStack": [ + { + "type": "struct", + "value": [] + }, + { + "type": "struct", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Map,Map]=FAULT", + "script": [ + "NEWMAP", + "DUP", + "XOR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "XOR", + "evaluationStack": [ + { + "type": "map", + "value": {} + }, + { + "type": "map", + "value": {} + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [ByteString,ByteString]=0", + "script": [ + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "XOR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 38, + "nextInstruction": "XOR", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + }, + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 39, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 0 + } + ] + } + } + ] + }, + { + "name": "Real test [ByteString,ByteString]=2381976568446569244243622252022377480192", + "script": [ + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000124", + "XOR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 38, + "nextInstruction": "XOR", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000124" + }, + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 39, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": "2381976568446569244243622252022377480192" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": "2381976568446569244243622252022377480192" + } + ] + } + } + ] + }, + { + "name": "Real test [ByteString,ByteString]=11959069470373746643343180651838589370368", + "script": [ + "PUSHDATA1", + "0x11", + "0x0000000000000000000000000000000123", + "PUSHDATA1", + "0x10", + "0x00000000000000000000000000000124", + "XOR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 37, + "nextInstruction": "XOR", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x00000000000000000000000000000124" + }, + { + "type": "ByteString", + "value": "0x0000000000000000000000000000000123" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 38, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Integer", + "value": "11959069470373746643343180651838589370368" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": "11959069470373746643343180651838589370368" + } + ] + } + } + ] + }, + { + "name": "Real test with clone [Interop,Interop]=FAULT", + "script": [ + "SYSCALL", + "0x77777777", + "DUP", + "XOR" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "DUP", + "evaluationStack": [ + { + "type": "interop", + "value": "Object" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "XOR", + "evaluationStack": [ + { + "type": "interop", + "value": "Object" + }, + { + "type": "interop", + "value": "Object" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Buffer,Buffer]=FAULT", + "script": [ + "PUSHDATA1", + "0x01AA", + "CONVERT", + "0x30", + "PUSHDATA1", + "0x01BB", + "CONVERT", + "0x30", + "XOR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 10, + "nextInstruction": "XOR", + "evaluationStack": [ + { + "type": "Buffer", + "value": "0xBB" + }, + { + "type": "Buffer", + "value": "0xAA" + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/ABORT.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/ABORT.json new file mode 100644 index 0000000000..17c3a35e2e --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/ABORT.json @@ -0,0 +1,22 @@ +{ + "category": "Control", + "name": "ABORT", + "tests": [ + { + "name": "Basic Test", + "script": [ + "ABORT" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/ABORTMSG.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/ABORTMSG.json new file mode 100644 index 0000000000..a7e795eb2c --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/ABORTMSG.json @@ -0,0 +1,26 @@ +{ + "category": "Control", + "name": "ABORTMSG", + "tests": [ + { + "name": "Basic Test", + "script": [ + "PUSHDATA1", + "0x03", + "0x4e454f", + "ABORTMSG" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT", + "exceptionMessage": "ABORTMSG is executed. Reason: NEO" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/ASSERT.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/ASSERT.json new file mode 100644 index 0000000000..bb7d14c5e8 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/ASSERT.json @@ -0,0 +1,42 @@ +{ + "category": "Control", + "name": "ASSERT", + "tests": [ + { + "name": "Fault Test", + "script": [ + "PUSH0", + "ASSERT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Halt Test", + "script": [ + "PUSH1", + "ASSERT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "execute" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/ASSERTMSG.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/ASSERTMSG.json new file mode 100644 index 0000000000..fc4c572ec3 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/ASSERTMSG.json @@ -0,0 +1,47 @@ +{ + "category": "Control", + "name": "ASSERTMSG", + "tests": [ + { + "name": "Fault Test", + "script": [ + "PUSH0", + "PUSHDATA1", + "0x04", + "0x4641494c", + "ASSERTMSG" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT", + "exceptionMessage": "ASSERTMSG is executed with false result. Reason: FAIL" + } + } + ] + }, + { + "name": "Halt Test", + "script": [ + "PUSH1", + "PUSHDATA1", + "0x04", + "0x50415353", + "ASSERTMSG" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/CALL.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/CALL.json new file mode 100644 index 0000000000..1b717c3a66 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/CALL.json @@ -0,0 +1,134 @@ +{ + "category": "Control", + "name": "CALL", + "tests": [ + { + "name": "Error negative", + "script": [ + "CALL", + "0xFF00" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Error out of bounds", + "script": [ + "CALL", + "0x0A" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "CALL", + "0x03", + "RET", + "PUSH0", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "PUSH0" + }, + { + "instructionPointer": 2, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + }, + { + "instructionPointer": 2, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/CALLA.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/CALLA.json new file mode 100644 index 0000000000..627442a5f1 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/CALLA.json @@ -0,0 +1,158 @@ +{ + "category": "Control", + "name": "CALLA", + "tests": [ + { + "name": "Wrong type", + "script": [ + "PUSH2", + "CALLA" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "CALLA", + "evaluationStack": [ + { + "type": "integer", + "value": 2 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "PUSHA", + "0x07000000", + "CALLA", + "RET", + "PUSH0", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "CALLA", + "evaluationStack": [ + { + "type": "pointer", + "value": 7 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 7, + "nextInstruction": "PUSH0" + }, + { + "instructionPointer": 6, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 8, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + }, + { + "instructionPointer": 6, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/CALL_L.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/CALL_L.json new file mode 100644 index 0000000000..5f60bda0ca --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/CALL_L.json @@ -0,0 +1,134 @@ +{ + "category": "Control", + "name": "CALL_L", + "tests": [ + { + "name": "Error negative", + "script": [ + "CALL_L", + "0x000000FF" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Error out of bounds", + "script": [ + "CALL_L", + "0x0A000000" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "CALL_L", + "0x06000000", + "RET", + "PUSH0", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "PUSH0" + }, + { + "instructionPointer": 5, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 7, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + }, + { + "instructionPointer": 5, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMP.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMP.json new file mode 100644 index 0000000000..172e50b956 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMP.json @@ -0,0 +1,74 @@ +{ + "category": "Control", + "name": "JMP", + "tests": [ + { + "name": "Out of range [<0]", + "script": [ + "JMP", + "0xff", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Out of range [>length]", + "script": [ + "JMP", + "0x7f", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "JMP", + "0x02", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPEQ.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPEQ.json new file mode 100644 index 0000000000..3e7a3144ba --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPEQ.json @@ -0,0 +1,307 @@ +{ + "category": "Control", + "name": "JMPEQ", + "tests": [ + { + "name": "Without all items", + "script": [ + "JMPEQ", + "0x00" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without one", + "script": [ + "PUSH1", + "JMPEQ", + "0x00" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [>]", + "script": [ + "PUSH1", + "PUSH0", + "JMPEQ", + "0x03", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH0", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPEQ", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "NOP" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Real test [==]", + "script": [ + "PUSH1", + "PUSH1", + "JMPEQ", + "0x03", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH1", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPEQ", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Real test [<]", + "script": [ + "PUSH0", + "PUSH1", + "JMPEQ", + "0x03", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH1", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPEQ", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "NOP" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPEQ_L.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPEQ_L.json new file mode 100644 index 0000000000..78ed4a0e70 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPEQ_L.json @@ -0,0 +1,307 @@ +{ + "category": "Control", + "name": "JMPEQ_L", + "tests": [ + { + "name": "Without all items", + "script": [ + "JMPEQ_L", + "0x00000000" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without one", + "script": [ + "PUSH1", + "JMPEQ_L", + "0x00000000" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [>]", + "script": [ + "PUSH1", + "PUSH0", + "JMPEQ_L", + "0x06000000", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH0", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPEQ_L", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 7, + "nextInstruction": "NOP" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 8, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Real test [==]", + "script": [ + "PUSH1", + "PUSH1", + "JMPEQ_L", + "0x06000000", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH1", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPEQ_L", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 8, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Real test [<]", + "script": [ + "PUSH0", + "PUSH1", + "JMPEQ_L", + "0x06000000", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH1", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPEQ_L", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 7, + "nextInstruction": "NOP" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 8, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPGE.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPGE.json new file mode 100644 index 0000000000..84f7d00c59 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPGE.json @@ -0,0 +1,293 @@ +{ + "category": "Control", + "name": "JMPGE", + "tests": [ + { + "name": "Without all items", + "script": [ + "JMPGE", + "0x00" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without one", + "script": [ + "PUSH1", + "JMPGE", + "0x00" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [>]", + "script": [ + "PUSH1", + "PUSH0", + "JMPGE", + "0x03", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH0", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPGE", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Real test [==]", + "script": [ + "PUSH1", + "PUSH1", + "JMPGE", + "0x03", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH1", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPGE", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Real test [<]", + "script": [ + "PUSH0", + "PUSH1", + "JMPGE", + "0x03", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH1", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPGE", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "NOP" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPGE_L.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPGE_L.json new file mode 100644 index 0000000000..2fa99ff25d --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPGE_L.json @@ -0,0 +1,293 @@ +{ + "category": "Control", + "name": "JMPGE_L", + "tests": [ + { + "name": "Without all items", + "script": [ + "JMPGE_L", + "0x00000000" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without one", + "script": [ + "PUSH1", + "JMPGE_L", + "0x00000000" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [>]", + "script": [ + "PUSH1", + "PUSH0", + "JMPGE_L", + "0x06000000", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH0", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPGE_L", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 8, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Real test [==]", + "script": [ + "PUSH1", + "PUSH1", + "JMPGE_L", + "0x06000000", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH1", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPGE_L", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 8, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Real test [<]", + "script": [ + "PUSH0", + "PUSH1", + "JMPGE_L", + "0x06000000", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH1", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPGE_L", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 7, + "nextInstruction": "NOP" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 8, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPGT.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPGT.json new file mode 100644 index 0000000000..c4ca491cca --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPGT.json @@ -0,0 +1,307 @@ +{ + "category": "Control", + "name": "JMPGT", + "tests": [ + { + "name": "Without all items", + "script": [ + "JMPGT", + "0x00" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without one", + "script": [ + "PUSH1", + "JMPGT", + "0x00" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [>]", + "script": [ + "PUSH1", + "PUSH0", + "JMPGT", + "0x03", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH0", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPGT", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Real test [==]", + "script": [ + "PUSH1", + "PUSH1", + "JMPGT", + "0x03", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH1", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPGT", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "NOP" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Real test [<]", + "script": [ + "PUSH0", + "PUSH1", + "JMPGT", + "0x03", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH1", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPGT", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "NOP" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPGT_L.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPGT_L.json new file mode 100644 index 0000000000..5523aa87c9 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPGT_L.json @@ -0,0 +1,307 @@ +{ + "category": "Control", + "name": "JMPGT_L", + "tests": [ + { + "name": "Without all items", + "script": [ + "JMPGT_L", + "0x00000000" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without one", + "script": [ + "PUSH1", + "JMPGT_L", + "0x00000000" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [>]", + "script": [ + "PUSH1", + "PUSH0", + "JMPGT_L", + "0x06000000", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH0", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPGT_L", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 8, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Real test [==]", + "script": [ + "PUSH1", + "PUSH1", + "JMPGT_L", + "0x06000000", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH1", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPGT_L", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 7, + "nextInstruction": "NOP" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 8, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Real test [<]", + "script": [ + "PUSH0", + "PUSH1", + "JMPGT_L", + "0x06000000", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH1", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPGT_L", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 7, + "nextInstruction": "NOP" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 8, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPIF.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPIF.json new file mode 100644 index 0000000000..121c8b76c7 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPIF.json @@ -0,0 +1,145 @@ +{ + "category": "Control", + "name": "JMPIF", + "tests": [ + { + "name": "Without items", + "script": [ + "JMPIF", + "0x00" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [1]", + "script": [ + "PUSH1", + "JMPIF", + "0x03", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "JMPIF", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Real test [0]", + "script": [ + "PUSH0", + "JMPIF", + "0x03", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "JMPIF", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "NOP" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPIFNOT.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPIFNOT.json new file mode 100644 index 0000000000..e11227ce40 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPIFNOT.json @@ -0,0 +1,145 @@ +{ + "category": "Control", + "name": "JMPIFNOT", + "tests": [ + { + "name": "Without items", + "script": [ + "JMPIFNOT", + "0x00" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [1]", + "script": [ + "PUSH1", + "JMPIFNOT", + "0x03", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "JMPIFNOT", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "NOP" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Real test [0]", + "script": [ + "PUSH0", + "JMPIFNOT", + "0x03", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "JMPIFNOT", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPIFNOT_L.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPIFNOT_L.json new file mode 100644 index 0000000000..90e37bee74 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPIFNOT_L.json @@ -0,0 +1,145 @@ +{ + "category": "Control", + "name": "JMPIFNOT_L", + "tests": [ + { + "name": "Without items", + "script": [ + "JMPIFNOT_L", + "0x00000000" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [1]", + "script": [ + "PUSH1", + "JMPIFNOT_L", + "0x06000000", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "JMPIFNOT_L", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "NOP" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 7, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Real test [0]", + "script": [ + "PUSH0", + "JMPIFNOT_L", + "0x06000000", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "JMPIFNOT_L", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 7, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPIF_L.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPIF_L.json new file mode 100644 index 0000000000..a9d0e1e352 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPIF_L.json @@ -0,0 +1,145 @@ +{ + "category": "Control", + "name": "JMPIF_L", + "tests": [ + { + "name": "Without items", + "script": [ + "JMPIF_L", + "0x00000000" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [1]", + "script": [ + "PUSH1", + "JMPIF_L", + "0x06000000", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "JMPIF_L", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 7, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Real test [0]", + "script": [ + "PUSH0", + "JMPIF_L", + "0x06000000", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "JMPIF_L", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "NOP" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 7, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPLE.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPLE.json new file mode 100644 index 0000000000..5325d60cf2 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPLE.json @@ -0,0 +1,293 @@ +{ + "category": "Control", + "name": "JMPLE", + "tests": [ + { + "name": "Without all items", + "script": [ + "JMPLE", + "0x00" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without one", + "script": [ + "PUSH1", + "JMPLE", + "0x00" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [>]", + "script": [ + "PUSH1", + "PUSH0", + "JMPLE", + "0x03", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH0", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPLE", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "NOP" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Real test [==]", + "script": [ + "PUSH1", + "PUSH1", + "JMPLE", + "0x03", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH1", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPLE", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Real test [<]", + "script": [ + "PUSH0", + "PUSH1", + "JMPLE", + "0x03", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH1", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPLE", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPLE_L.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPLE_L.json new file mode 100644 index 0000000000..3ccc3c06e8 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPLE_L.json @@ -0,0 +1,293 @@ +{ + "category": "Control", + "name": "JMPLE_L", + "tests": [ + { + "name": "Without all items", + "script": [ + "JMPLE_L", + "0x00000000" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without one", + "script": [ + "PUSH1", + "JMPLE_L", + "0x00000000" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [>]", + "script": [ + "PUSH1", + "PUSH0", + "JMPLE_L", + "0x06000000", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH0", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPLE_L", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 7, + "nextInstruction": "NOP" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 8, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Real test [==]", + "script": [ + "PUSH1", + "PUSH1", + "JMPLE_L", + "0x06000000", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH1", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPLE_L", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 8, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Real test [<]", + "script": [ + "PUSH0", + "PUSH1", + "JMPLE_L", + "0x06000000", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH1", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPLE_L", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 8, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPLT.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPLT.json new file mode 100644 index 0000000000..72946b6c6a --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPLT.json @@ -0,0 +1,307 @@ +{ + "category": "Control", + "name": "JMPLT", + "tests": [ + { + "name": "Without all items", + "script": [ + "JMPLT", + "0x00" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without one", + "script": [ + "PUSH1", + "JMPLT", + "0x00" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [>]", + "script": [ + "PUSH1", + "PUSH0", + "JMPLT", + "0x03", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH0", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPLT", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "NOP" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Real test [==]", + "script": [ + "PUSH1", + "PUSH1", + "JMPLT", + "0x03", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH1", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPLT", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "NOP" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Real test [<]", + "script": [ + "PUSH0", + "PUSH1", + "JMPLT", + "0x03", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH1", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPLT", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPLT_L.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPLT_L.json new file mode 100644 index 0000000000..196b455d0a --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPLT_L.json @@ -0,0 +1,307 @@ +{ + "category": "Control", + "name": "JMPLT_L", + "tests": [ + { + "name": "Without all items", + "script": [ + "JMPLT_L", + "0x00000000" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without one", + "script": [ + "PUSH1", + "JMPLT_L", + "0x00000000" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [>]", + "script": [ + "PUSH1", + "PUSH0", + "JMPLT_L", + "0x06000000", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH0", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPLT_L", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 7, + "nextInstruction": "NOP" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 8, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Real test [==]", + "script": [ + "PUSH1", + "PUSH1", + "JMPLT_L", + "0x06000000", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH1", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPLT_L", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 7, + "nextInstruction": "NOP" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 8, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Real test [<]", + "script": [ + "PUSH0", + "PUSH1", + "JMPLT_L", + "0x06000000", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH1", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPLT_L", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 8, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPNE.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPNE.json new file mode 100644 index 0000000000..53498bc05b --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPNE.json @@ -0,0 +1,293 @@ +{ + "category": "Control", + "name": "JMPNE", + "tests": [ + { + "name": "Without all items", + "script": [ + "JMPNE", + "0x00" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without one", + "script": [ + "PUSH1", + "JMPNE", + "0x00" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [>]", + "script": [ + "PUSH1", + "PUSH0", + "JMPNE", + "0x03", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH0", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPNE", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Real test [==]", + "script": [ + "PUSH1", + "PUSH1", + "JMPNE", + "0x03", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH1", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPNE", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "NOP" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Real test [<]", + "script": [ + "PUSH0", + "PUSH1", + "JMPNE", + "0x03", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH1", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPNE", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPNE_L.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPNE_L.json new file mode 100644 index 0000000000..a1adb96021 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMPNE_L.json @@ -0,0 +1,293 @@ +{ + "category": "Control", + "name": "JMPNE_L", + "tests": [ + { + "name": "Without all items", + "script": [ + "JMPNE_L", + "0x00000000" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without one", + "script": [ + "PUSH1", + "JMPNE_L", + "0x00000000" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [>]", + "script": [ + "PUSH1", + "PUSH0", + "JMPNE_L", + "0x06000000", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH0", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPNE_L", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 8, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Real test [==]", + "script": [ + "PUSH1", + "PUSH1", + "JMPNE_L", + "0x06000000", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH1", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPNE_L", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 7, + "nextInstruction": "NOP" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 8, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Real test [<]", + "script": [ + "PUSH0", + "PUSH1", + "JMPNE_L", + "0x06000000", + "NOP", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "PUSH1", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "JMPNE_L", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 8, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMP_L.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMP_L.json new file mode 100644 index 0000000000..4f6157d1de --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/JMP_L.json @@ -0,0 +1,74 @@ +{ + "category": "Control", + "name": "JMP_L", + "tests": [ + { + "name": "Out of range [<0]", + "script": [ + "JMP_L", + "0xffffffff", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Out of range [>length]", + "script": [ + "JMP_L", + "0xffffff7f", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "JMP_L", + "0x05000000", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/NOP.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/NOP.json new file mode 100644 index 0000000000..3564f8afa2 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/NOP.json @@ -0,0 +1,96 @@ +{ + "category": "Control", + "name": "NOP", + "tests": [ + { + "name": "Real test", + "script": [ + "NOP", + "NOP", + "NOP", + "NOP", + "NOP" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "NOP" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "NOP" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "NOP" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "NOP" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/RET.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/RET.json new file mode 100644 index 0000000000..993f8af213 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/RET.json @@ -0,0 +1,22 @@ +{ + "category": "Control", + "name": "RET", + "tests": [ + { + "name": "Real test", + "script": [ + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/SYSCALL.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/SYSCALL.json new file mode 100644 index 0000000000..c99da78b24 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/SYSCALL.json @@ -0,0 +1,143 @@ +{ + "category": "Control", + "name": "SYSCALL", + "tests": [ + { + "name": "Syscall that does not exist", + "script": [ + "SYSCALL", + "0x00" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Wrong script", + "script": [ + "SYSCALL", + "0x2a537973", + "DEPTH", + "CALL_L", + "0x6d2e0000", + "PUSHDATA1", + "0x457865637574696f6e456e67696e652e476574536372697074436f6e7461696e6572" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [IMessageProvider]", + "script": [ + "SYSCALL", + "0x77777777" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "interop", + "value": "Object" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "interop", + "value": "Object" + } + ] + } + } + ] + }, + { + "name": "Wrong script", + "script": [ + "SYSCALL", + "0xfdffff00" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Wrong script", + "script": [ + "SYSCALL", + "0xfeffffff", + "0xff", + "PUSH0" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Wrong script", + "script": [ + "SYSCALL", + "0xffffffff", + "0xffffffffff", + "PUSH0" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/THROW.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/THROW.json new file mode 100644 index 0000000000..a382516154 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/THROW.json @@ -0,0 +1,24 @@ +{ + "category": "Control", + "name": "THROW", + "tests": [ + { + "name": "Fault Test", + "script": [ + "PUSH0", + "THROW" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH.json new file mode 100644 index 0000000000..d08f084d95 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH.json @@ -0,0 +1,175 @@ +{ + "category": "Control", + "name": "TRY_CATCH", + "tests": [ + { + "name": "try catch with syscall exception", + "script": [ + "TRY", + "0x0a00", + "SYSCALL", + "0xdeaddead", + "ENDTRY", + "0x05", + "PUSH1", + "ENDTRY", + "0x02", + "PUSH2" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 11, + "nextInstruction": "ENDTRY", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "ByteString", + "value": "0x6572726f72" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + }, + { + "type": "ByteString", + "value": "0x6572726f72" + } + ] + } + } + ] + }, + { + "name": "try catch without exception", + "script": [ + "TRY", + "0x0600", + "PUSH0", + "ENDTRY", + "0x05", + "PUSH3", + "ENDTRY", + "0x02", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 9, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + } + ] + }, + { + "name": "try catch with exception", + "script": [ + "TRY", + "0x0700", + "PUSH0", + "THROW", + "ENDTRY", + "0x05", + "PUSH1", + "ENDTRY", + "0x02", + "PUSH2" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 7, + "nextInstruction": "PUSH1", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 10, + "nextInstruction": "PUSH2", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH_FINALLY.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH_FINALLY.json new file mode 100644 index 0000000000..769829fc3d --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH_FINALLY.json @@ -0,0 +1,72 @@ +{ + "category": "Control", + "name": "TRY_CATCH_FINALLY", + "tests": [ + { + "name": "try catch finally without exception", + "script": [ + "TRY", + "0x080a", + "PUSH0", + "ENDTRY", + "0x08", + "RET", + "PUSH2", + "ENDTRY", + "0x04", + "PUSH3", + "ENDFINALLY", + "PUSH4" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 10, + "nextInstruction": "PUSH3", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 12, + "nextInstruction": "PUSH4", + "evaluationStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH_FINALLY10.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH_FINALLY10.json new file mode 100644 index 0000000000..fae60b985f --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH_FINALLY10.json @@ -0,0 +1,25 @@ +{ + "category": "Control", + "name": "TRY_CATCH_FINALLY", + "tests": [ + { + "name": "try + finally without ENDTRY", + "script": [ + "TRY", + "0x0003", + "ENDFINALLY" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH_FINALLY2.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH_FINALLY2.json new file mode 100644 index 0000000000..37299f2423 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH_FINALLY2.json @@ -0,0 +1,103 @@ +{ + "category": "Control", + "name": "TRY_CATCH_FINALLY", + "tests": [ + { + "name": "try catch finally with exception", + "script": [ + "TRY", + "0x080C", + "PUSH0", + "THROW", + "ENDTRY", + "0x06", + "RET", + "PUSH1", + "ENDTRY", + "0x02", + "RET", + "PUSH2", + "ENDFINALLY", + "PUSH3" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 8, + "nextInstruction": "PUSH1", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 12, + "nextInstruction": "PUSH2", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 11, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH_FINALLY3.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH_FINALLY3.json new file mode 100644 index 0000000000..b6cd601ff2 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH_FINALLY3.json @@ -0,0 +1,111 @@ +{ + "category": "Control", + "name": "TRY_CATCH_FINALLY", + "tests": [ + { + "name": "try{ try{ throw }catch{ }}catch{ }finally{ }", + "script": [ + "TRY", + "0x0e13", + "TRY", + "0x0700", + "PUSH0", + "THROW", + "ENDTRY", + "0x01", + "PUSH1", + "ENDTRY", + "0x02", + "ENDTRY", + "0x02", + "RET", + "PUSH2", + "ENDTRY", + "0x04", + "PUSH3", + "ENDFINALLY", + "PUSH4" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 10, + "nextInstruction": "PUSH1", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 13, + "nextInstruction": "ENDTRY", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 15, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH_FINALLY4.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH_FINALLY4.json new file mode 100644 index 0000000000..710cadb12c --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH_FINALLY4.json @@ -0,0 +1,117 @@ +{ + "category": "Control", + "name": "TRY_CATCH_FINALLY", + "tests": [ + { + "name": "try{ try{ throw }catch{ throw } }catch{ }finally{ }", + "script": [ + "TRY", + "0x1014", + "TRY", + "0x0700", + "PUSH0", + "THROW", + "ENDTRY", + "0x01", + "PUSH1", + "THROW", + "ENDTRY", + "0x04", + "ENDTRY", + "0x01", + "PUSH2", + "ENDTRY", + "0x02", + "RET", + "PUSH3", + "ENDFINALLY", + "PUSH4" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 10, + "nextInstruction": "PUSH1", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 16, + "nextInstruction": "PUSH2", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 19, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH_FINALLY5.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH_FINALLY5.json new file mode 100644 index 0000000000..6cb08caa3c --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH_FINALLY5.json @@ -0,0 +1,35 @@ +{ + "category": "Control", + "name": "TRY_CATCH_FINALLY", + "tests": [ + { + "name": "try{ assert false }catch{ push2 }finally{ push3 }", + "script": [ + "TRY", + "0x0608", + "PUSH0", + "ASSERT", + "ENDTRY", + "0x01", + "PUSH2", + "ENDTRY", + "0x01", + "PUSH3", + "ENDFINALLY", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH_FINALLY6.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH_FINALLY6.json new file mode 100644 index 0000000000..40b5bf2667 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH_FINALLY6.json @@ -0,0 +1,33 @@ +{ + "category": "Control", + "name": "TRY_CATCH_FINALLY", + "tests": [ + { + "name": "try{ abort }catch{ push2 }finally{ push3 }", + "script": [ + "TRY", + "0x0507", + "ABORT", + "ENDTRY", + "0x01", + "PUSH2", + "ENDTRY", + "0x01", + "PUSH3", + "ENDFINALLY", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH_FINALLY7.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH_FINALLY7.json new file mode 100644 index 0000000000..766e88f839 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH_FINALLY7.json @@ -0,0 +1,55 @@ +{ + "category": "Control", + "name": "TRY_CATCH_FINALLY", + "tests": [ + { + "name": "try{ throw }catch{ abort }finally{ push3 }", + "script": [ + "TRY", + "0x070a", + "PUSH0", + "THROW", + "ENDTRY", + "0x01", + "ABORT", + "ENDTRY", + "0x01", + "PUSH3", + "ENDFINALLY", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer":7, + "nextInstruction": "ABORT", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH_FINALLY8.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH_FINALLY8.json new file mode 100644 index 0000000000..93f2f84c26 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH_FINALLY8.json @@ -0,0 +1,101 @@ +{ + "category": "Control", + "name": "TRY_CATCH_FINALLY", + "tests": [ + { + "name": "try{ throw }catch{ throw }finally{ push3 }", + "script": [ + "TRY", + "0x070b", + "PUSH0", + "THROW", + "ENDTRY", + "0x01", + "PUSH2", + "THROW", + "ENDTRY", + "0x01", + "PUSH3", + "ENDFINALLY", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 7, + "nextInstruction": "PUSH2", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 11, + "nextInstruction": "PUSH3", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 12, + "nextInstruction": "ENDFINALLY", + "evaluationStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH_FINALLY9.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH_FINALLY9.json new file mode 100644 index 0000000000..fe766a025a --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_CATCH_FINALLY9.json @@ -0,0 +1,98 @@ +{ + "category": "Control", + "name": "TRY_CATCH_FINALLY", + "tests": [ + { + "name": "try{ PUSH0, call A: PUSH1 { call B: PUSH2 throw an exception } }catch{ PUSH3 }", + "script": [ + "TRY", + "0x0f00", + "PUSH0", + "CALL", + "0x03", + "RET", + "PUSH1", + "CALL", + "0x02", + "PUSH2", + "THROW", + "RET", + "ENDTRY", + "0x01", + "PUSH3", + "ENDTRY", + "0x02", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 15, + "nextInstruction": "PUSH3", + "evaluationStack": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 18, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_FINALLY.json b/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_FINALLY.json new file mode 100644 index 0000000000..abb4788a32 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Control/TRY_FINALLY.json @@ -0,0 +1,49 @@ +{ + "category": "Control", + "name": "TRY_FINALLY", + "tests": [ + { + "name": "try finally with exception", + "script": [ + "TRY", + "0x0009", + "PUSH0", + "THROW", + "ENDTRY", + "0x01", + "JMP", + "0x03", + "PUSH1", + "ENDFINALLY", + "PUSH2" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 9, + "nextInstruction": "PUSH1" + } + ] + } + }, + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Push/PUSHA.json b/tests/Neo.VM.Tests/Tests/OpCodes/Push/PUSHA.json new file mode 100644 index 0000000000..c6a0bf4c4f --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Push/PUSHA.json @@ -0,0 +1,86 @@ +{ + "category": "Push", + "name": "PUSHA", + "tests": [ + { + "name": "Out of range [-1]", + "script": [ + "PUSHA", + "0xffffffff" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Out of range [>length]", + "script": [ + "PUSHA", + "0xffffff7f" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "PUSHA", + "0x00000000" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "pointer", + "value": 0 + } + ] + } + } + ] + }, + { + "name": "Real test [=length]", + "script": [ + "PUSHA", + "0x05000000" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "pointer", + "value": 5 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Push/PUSHDATA1.json b/tests/Neo.VM.Tests/Tests/OpCodes/Push/PUSHDATA1.json new file mode 100644 index 0000000000..5831718b95 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Push/PUSHDATA1.json @@ -0,0 +1,47 @@ +{ + "category": "Push", + "name": "PUSHDATA1", + "tests": [ + { + "name": "Good definition", + "script": [ + "PUSHDATA1", + "0x04", + "0x01020304" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "ByteString", + "value": "0x01020304" + } + ] + } + } + ] + }, + { + "name": "Without enough length", + "script": [ + "PUSHDATA1", + "0x0501020304" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Push/PUSHDATA2.json b/tests/Neo.VM.Tests/Tests/OpCodes/Push/PUSHDATA2.json new file mode 100644 index 0000000000..b31e47fb74 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Push/PUSHDATA2.json @@ -0,0 +1,47 @@ +{ + "category": "Push", + "name": "PUSHDATA2", + "tests": [ + { + "name": "Good definition", + "script": [ + "PUSHDATA2", + "0x0400", + "0x01020304" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "ByteString", + "value": "0x01020304" + } + ] + } + } + ] + }, + { + "name": "Without enough length", + "script": [ + "PUSHDATA2", + "0x050001020304" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Push/PUSHDATA4.json b/tests/Neo.VM.Tests/Tests/OpCodes/Push/PUSHDATA4.json new file mode 100644 index 0000000000..d61a42b3a4 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Push/PUSHDATA4.json @@ -0,0 +1,99 @@ +{ + "category": "Push", + "name": "PUSHDATA4", + "tests": [ + { + "name": "More length than script", + "script": [ + "PUSHDATA4", + "0x00080000" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Negative length", + "script": [ + "PUSHDATA4", + "0xffffffff" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Good definition", + "script": [ + "PUSHDATA4", + "0x04000000", + "0x01020304" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "ByteString", + "value": "0x01020304" + } + ] + } + } + ] + }, + { + "name": "Without enough length", + "script": [ + "PUSHDATA4", + "0x0500000001020304" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Max length (Parse Instruction Error)", + "script": [ + "PUSHDATA4", + "0x01001000", + "0xFF" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Push/PUSHINT8_to_PUSHINT256.json b/tests/Neo.VM.Tests/Tests/OpCodes/Push/PUSHINT8_to_PUSHINT256.json new file mode 100644 index 0000000000..72cca284bb --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Push/PUSHINT8_to_PUSHINT256.json @@ -0,0 +1,59 @@ +{ + "category": "Push", + "name": "PUSHINT8 to PUSHINT256", + "tests": [ + { + "name": "Basic Test", + "script": [ + "PUSHINT8", + "0xff", + "PUSHINT16", + "0xfeff", + "PUSHINT32", + "0xfdffffff", + "PUSHINT64", + "0xfcffffffffffffff", + "PUSHINT128", + "0xfbffffffffffffffffffffffffffffff", + "PUSHINT256", + "0xfaffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": -6 + }, + { + "type": "integer", + "value": -5 + }, + { + "type": "integer", + "value": -4 + }, + { + "type": "integer", + "value": -3 + }, + { + "type": "integer", + "value": -2 + }, + { + "type": "integer", + "value": -1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Push/PUSHM1_to_PUSH16.json b/tests/Neo.VM.Tests/Tests/OpCodes/Push/PUSHM1_to_PUSH16.json new file mode 100644 index 0000000000..3b7f06ed6b --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Push/PUSHM1_to_PUSH16.json @@ -0,0 +1,219 @@ +{ + "category": "Push", + "name": "From PUSHM1 to PUSH16 [-1 to 16]", + "tests": [ + { + "name": "Basic Test", + "script": [ + "PUSHM1", + "PUSH0", + "PUSH1", + "PUSH2", + "PUSH3", + "PUSH4", + "PUSH5", + "PUSH6", + "PUSH7", + "PUSH8", + "PUSH9", + "PUSH10", + "PUSH11", + "PUSH12", + "PUSH13", + "PUSH14", + "PUSH15", + "PUSH16", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 18, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 16 + }, + { + "type": "integer", + "value": 15 + }, + { + "type": "integer", + "value": 14 + }, + { + "type": "integer", + "value": 13 + }, + { + "type": "integer", + "value": 12 + }, + { + "type": "integer", + "value": 11 + }, + { + "type": "integer", + "value": 10 + }, + { + "type": "integer", + "value": 9 + }, + { + "type": "integer", + "value": 8 + }, + { + "type": "integer", + "value": 7 + }, + { + "type": "integer", + "value": 6 + }, + { + "type": "integer", + "value": 5 + }, + { + "type": "integer", + "value": 4 + }, + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": -1 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 16 + }, + { + "type": "integer", + "value": 15 + }, + { + "type": "integer", + "value": 14 + }, + { + "type": "integer", + "value": 13 + }, + { + "type": "integer", + "value": 12 + }, + { + "type": "integer", + "value": 11 + }, + { + "type": "integer", + "value": 10 + }, + { + "type": "integer", + "value": 9 + }, + { + "type": "integer", + "value": 8 + }, + { + "type": "integer", + "value": 7 + }, + { + "type": "integer", + "value": 6 + }, + { + "type": "integer", + "value": 5 + }, + { + "type": "integer", + "value": 4 + }, + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": -1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Push/PUSHNULL.json b/tests/Neo.VM.Tests/Tests/OpCodes/Push/PUSHNULL.json new file mode 100644 index 0000000000..5b5247edb1 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Push/PUSHNULL.json @@ -0,0 +1,46 @@ +{ + "category": "Push", + "name": "PUSHNULL", + "tests": [ + { + "name": "Good definition", + "script": [ + "PUSHNULL" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "null" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "null" + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/INITSLOT.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/INITSLOT.json new file mode 100644 index 0000000000..458463ea55 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/INITSLOT.json @@ -0,0 +1,206 @@ +{ + "category": "Slot", + "name": "INITSLOT", + "tests": [ + { + "name": "Without enough items", + "script": [ + "INITSLOT", + "0x0101" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without 0 items", + "script": [ + "INITSLOT", + "0x0000" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [LocalVariables]", + "script": [ + "INITSLOT", + "0x0100" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "localVariables": [ + { + "type": "Null" + } + ] + } + ] + } + }, + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Real test [Arguments]", + "script": [ + "PUSH1", + "INITSLOT", + "0x0001" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "RET", + "arguments": [ + { + "type": "Integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Real test [LocalVariables + Arguments]", + "script": [ + "PUSH1", + "INITSLOT", + "0x0101" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "RET", + "localVariables": [ + { + "type": "Null" + } + ], + "arguments": [ + { + "type": "Integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Initialize twice", + "script": [ + "PUSH0", + "INITSLOT", + "0x0101", + "PUSH0", + "INITSLOT", + "0x0101" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "PUSH0", + "localVariables": [ + { + "type": "Null" + } + ], + "arguments": [ + { + "type": "Integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/INITSSLOT.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/INITSSLOT.json new file mode 100644 index 0000000000..e3a3e149f4 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/INITSSLOT.json @@ -0,0 +1,97 @@ +{ + "category": "Slot", + "name": "INITSSLOT", + "tests": [ + { + "name": "Without 0 items", + "script": [ + "INITSSLOT", + "0x00" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "INITSSLOT", + "0x01" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "RET", + "staticFields": [ + { + "type": "Null" + } + ] + } + ] + } + }, + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Initialize twice", + "script": [ + "INITSSLOT", + "0x01", + "INITSSLOT", + "0x02" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "INITSSLOT", + "staticFields": [ + { + "type": "Null" + } + ] + } + ] + } + }, + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDARG.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDARG.json new file mode 100644 index 0000000000..e3be41d38c --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDARG.json @@ -0,0 +1,69 @@ +{ + "category": "Slot", + "name": "LDARG", + "tests": [ + { + "name": "Without slot", + "script": [ + "LDARG", + "0x00" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "PUSH1", + "INITSLOT", + "0x0001", + "LDARG", + "0x01" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "PUSH1", + "INITSLOT", + "0x0001", + "LDARG", + "0x00" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDARG0.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDARG0.json new file mode 100644 index 0000000000..038e8db3a6 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDARG0.json @@ -0,0 +1,47 @@ +{ + "category": "Slot", + "name": "LDARG0", + "tests": [ + { + "name": "Without slot", + "script": [ + "LDARG0" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "PUSH1", + "INITSLOT", + "0x0001", + "LDARG0" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDARG1.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDARG1.json new file mode 100644 index 0000000000..522b1e68ac --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDARG1.json @@ -0,0 +1,67 @@ +{ + "category": "Slot", + "name": "LDARG1", + "tests": [ + { + "name": "Without slot", + "script": [ + "LDARG1" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "PUSH1", + "INITSLOT", + "0x0001", + "LDARG1" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "PUSH1", + "PUSH2", + "INITSLOT", + "0x0002", + "LDARG1" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDARG2.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDARG2.json new file mode 100644 index 0000000000..16ec5d743d --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDARG2.json @@ -0,0 +1,68 @@ +{ + "category": "Slot", + "name": "LDARG2", + "tests": [ + { + "name": "Without slot", + "script": [ + "LDARG2" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "PUSH1", + "INITSLOT", + "0x0001", + "LDARG2" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "PUSH1", + "PUSH2", + "PUSH3", + "INITSLOT", + "0x0003", + "LDARG2" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDARG3.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDARG3.json new file mode 100644 index 0000000000..5a4a844a1b --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDARG3.json @@ -0,0 +1,69 @@ +{ + "category": "Slot", + "name": "LDARG3", + "tests": [ + { + "name": "Without slot", + "script": [ + "LDARG3" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "PUSH1", + "INITSLOT", + "0x0001", + "LDARG3" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "PUSH1", + "PUSH2", + "PUSH3", + "PUSH4", + "INITSLOT", + "0x0004", + "LDARG3" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDARG4.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDARG4.json new file mode 100644 index 0000000000..4a091e5ff2 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDARG4.json @@ -0,0 +1,70 @@ +{ + "category": "Slot", + "name": "LDARG4", + "tests": [ + { + "name": "Without slot", + "script": [ + "LDARG4" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "PUSH1", + "INITSLOT", + "0x0001", + "LDARG4" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "PUSH1", + "PUSH2", + "PUSH3", + "PUSH4", + "PUSH5", + "INITSLOT", + "0x0005", + "LDARG4" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDARG5.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDARG5.json new file mode 100644 index 0000000000..1d0d706266 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDARG5.json @@ -0,0 +1,71 @@ +{ + "category": "Slot", + "name": "LDARG5", + "tests": [ + { + "name": "Without slot", + "script": [ + "LDARG5" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "PUSH1", + "INITSLOT", + "0x0001", + "LDARG5" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "PUSH1", + "PUSH2", + "PUSH3", + "PUSH4", + "PUSH5", + "PUSH6", + "INITSLOT", + "0x0006", + "LDARG5" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDARG6.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDARG6.json new file mode 100644 index 0000000000..56da5d948b --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDARG6.json @@ -0,0 +1,72 @@ +{ + "category": "Slot", + "name": "LDARG6", + "tests": [ + { + "name": "Without slot", + "script": [ + "LDARG6" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "PUSH1", + "INITSLOT", + "0x0001", + "LDARG6" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "PUSH1", + "PUSH2", + "PUSH3", + "PUSH4", + "PUSH5", + "PUSH6", + "PUSH7", + "INITSLOT", + "0x0007", + "LDARG6" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDLOC.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDLOC.json new file mode 100644 index 0000000000..87f165dbdf --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDLOC.json @@ -0,0 +1,70 @@ +{ + "category": "Slot", + "name": "LDLOC", + "tests": [ + { + "name": "Without slot", + "script": [ + "LDLOC", + "0x00" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "INITSLOT", + "0x0100", + "LDLOC", + "0x01" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "INITSLOT", + "0x0100", + "PUSH1", + "STLOC", + "0x00", + "LDLOC", + "0x00" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDLOC0.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDLOC0.json new file mode 100644 index 0000000000..294a7b8df0 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDLOC0.json @@ -0,0 +1,48 @@ +{ + "category": "Slot", + "name": "LDLOC0", + "tests": [ + { + "name": "Without slot", + "script": [ + "LDLOC0" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "INITSLOT", + "0x0100", + "PUSH1", + "STLOC0", + "LDLOC0" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDLOC1.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDLOC1.json new file mode 100644 index 0000000000..f9e648200a --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDLOC1.json @@ -0,0 +1,66 @@ +{ + "category": "Slot", + "name": "LDLOC1", + "tests": [ + { + "name": "Without slot", + "script": [ + "LDLOC1" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "INITSLOT", + "0x0100", + "LDLOC1" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "INITSLOT", + "0x0200", + "PUSH1", + "STLOC1", + "LDLOC1" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDLOC2.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDLOC2.json new file mode 100644 index 0000000000..075f338683 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDLOC2.json @@ -0,0 +1,66 @@ +{ + "category": "Slot", + "name": "LDLOC2", + "tests": [ + { + "name": "Without slot", + "script": [ + "LDLOC2" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "INITSLOT", + "0x0100", + "LDLOC2" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "INITSLOT", + "0x0300", + "PUSH1", + "STLOC2", + "LDLOC2" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDLOC3.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDLOC3.json new file mode 100644 index 0000000000..5463edaebc --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDLOC3.json @@ -0,0 +1,66 @@ +{ + "category": "Slot", + "name": "LDLOC3", + "tests": [ + { + "name": "Without slot", + "script": [ + "LDLOC3" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "INITSLOT", + "0x0100", + "LDLOC3" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "INITSLOT", + "0x0400", + "PUSH1", + "STLOC3", + "LDLOC3" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDLOC4.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDLOC4.json new file mode 100644 index 0000000000..23a616975d --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDLOC4.json @@ -0,0 +1,66 @@ +{ + "category": "Slot", + "name": "LDLOC4", + "tests": [ + { + "name": "Without slot", + "script": [ + "LDLOC4" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "INITSLOT", + "0x0100", + "LDLOC4" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "INITSLOT", + "0x0500", + "PUSH1", + "STLOC4", + "LDLOC4" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDLOC5.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDLOC5.json new file mode 100644 index 0000000000..9bde0a550a --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDLOC5.json @@ -0,0 +1,66 @@ +{ + "category": "Slot", + "name": "LDLOC5", + "tests": [ + { + "name": "Without slot", + "script": [ + "LDLOC5" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "INITSLOT", + "0x0100", + "LDLOC5" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "INITSLOT", + "0x0600", + "PUSH1", + "STLOC5", + "LDLOC5" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDLOC6.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDLOC6.json new file mode 100644 index 0000000000..edf7963263 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDLOC6.json @@ -0,0 +1,66 @@ +{ + "category": "Slot", + "name": "LDLOC6", + "tests": [ + { + "name": "Without slot", + "script": [ + "LDLOC6" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "INITSLOT", + "0x0100", + "LDLOC6" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "INITSLOT", + "0x0700", + "PUSH1", + "STLOC6", + "LDLOC6" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDSFLD.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDSFLD.json new file mode 100644 index 0000000000..311c0a5964 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDSFLD.json @@ -0,0 +1,70 @@ +{ + "category": "Slot", + "name": "LDSFLD", + "tests": [ + { + "name": "Without slot", + "script": [ + "LDSFLD", + "0x00" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "INITSSLOT", + "0x01", + "LDSFLD", + "0x01" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "INITSSLOT", + "0x01", + "PUSH1", + "STSFLD", + "0x00", + "LDSFLD", + "0x00" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDSFLD0.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDSFLD0.json new file mode 100644 index 0000000000..8a750b04f2 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDSFLD0.json @@ -0,0 +1,48 @@ +{ + "category": "Slot", + "name": "LDSFLD0", + "tests": [ + { + "name": "Without slot", + "script": [ + "LDSFLD0" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "INITSSLOT", + "0x01", + "PUSH1", + "STSFLD0", + "LDSFLD0" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDSFLD1.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDSFLD1.json new file mode 100644 index 0000000000..4289e1aa91 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDSFLD1.json @@ -0,0 +1,66 @@ +{ + "category": "Slot", + "name": "LDSFLD1", + "tests": [ + { + "name": "Without slot", + "script": [ + "LDSFLD1" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "INITSSLOT", + "0x01", + "LDSFLD1" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "INITSSLOT", + "0x02", + "PUSH1", + "STSFLD1", + "LDSFLD1" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDSFLD2.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDSFLD2.json new file mode 100644 index 0000000000..e03ab04d48 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDSFLD2.json @@ -0,0 +1,66 @@ +{ + "category": "Slot", + "name": "LDSFLD2", + "tests": [ + { + "name": "Without slot", + "script": [ + "LDSFLD2" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "INITSSLOT", + "0x01", + "LDSFLD2" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "INITSSLOT", + "0x03", + "PUSH1", + "STSFLD2", + "LDSFLD2" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDSFLD3.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDSFLD3.json new file mode 100644 index 0000000000..d10624f5a7 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDSFLD3.json @@ -0,0 +1,66 @@ +{ + "category": "Slot", + "name": "LDSFLD3", + "tests": [ + { + "name": "Without slot", + "script": [ + "LDSFLD3" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "INITSSLOT", + "0x01", + "LDSFLD3" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "INITSSLOT", + "0x04", + "PUSH1", + "STSFLD3", + "LDSFLD3" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDSFLD4.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDSFLD4.json new file mode 100644 index 0000000000..b405defe0f --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDSFLD4.json @@ -0,0 +1,66 @@ +{ + "category": "Slot", + "name": "LDSFLD4", + "tests": [ + { + "name": "Without slot", + "script": [ + "LDSFLD4" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "INITSSLOT", + "0x01", + "LDSFLD4" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "INITSSLOT", + "0x05", + "PUSH1", + "STSFLD4", + "LDSFLD4" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDSFLD5.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDSFLD5.json new file mode 100644 index 0000000000..c6b3ee058f --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDSFLD5.json @@ -0,0 +1,66 @@ +{ + "category": "Slot", + "name": "LDSFLD5", + "tests": [ + { + "name": "Without slot", + "script": [ + "LDSFLD5" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "INITSSLOT", + "0x01", + "LDSFLD5" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "INITSSLOT", + "0x06", + "PUSH1", + "STSFLD5", + "LDSFLD5" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDSFLD6.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDSFLD6.json new file mode 100644 index 0000000000..cd7ae9b500 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/LDSFLD6.json @@ -0,0 +1,66 @@ +{ + "category": "Slot", + "name": "LDSFLD6", + "tests": [ + { + "name": "Without slot", + "script": [ + "LDSFLD6" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "INITSSLOT", + "0x01", + "LDSFLD6" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "INITSSLOT", + "0x07", + "PUSH1", + "STSFLD6", + "LDSFLD6" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STARG.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STARG.json new file mode 100644 index 0000000000..bdf9f2f5c5 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STARG.json @@ -0,0 +1,72 @@ +{ + "category": "Slot", + "name": "STARG", + "tests": [ + { + "name": "Without slot", + "script": [ + "PUSH1", + "STARG", + "0x00" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "PUSH0", + "INITSLOT", + "0x0001", + "PUSH1", + "STARG", + "0x01" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "PUSH1", + "INITSLOT", + "0x0001", + "PUSH0", + "STARG", + "0x00" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "arguments": [ + { + "type": "Integer", + "value": 0 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STARG0.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STARG0.json new file mode 100644 index 0000000000..b5666c3754 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STARG0.json @@ -0,0 +1,49 @@ +{ + "category": "Slot", + "name": "STARG0", + "tests": [ + { + "name": "Without slot", + "script": [ + "PUSH1", + "STARG0" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "PUSH0", + "INITSLOT", + "0x0001", + "PUSH1", + "STARG0" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "arguments": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STARG1.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STARG1.json new file mode 100644 index 0000000000..da52006943 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STARG1.json @@ -0,0 +1,74 @@ +{ + "category": "Slot", + "name": "STARG1", + "tests": [ + { + "name": "Without slot", + "script": [ + "PUSH1", + "STARG1" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "PUSH0", + "INITSLOT", + "0x0001", + "PUSH1", + "STARG1" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "PUSH1", + "PUSH2", + "INITSLOT", + "0x0002", + "PUSH0", + "STARG1" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "arguments": [ + { + "type": "Integer", + "value": 1 + }, + { + "type": "Integer", + "value": 0 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STARG2.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STARG2.json new file mode 100644 index 0000000000..8c6952eedd --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STARG2.json @@ -0,0 +1,79 @@ +{ + "category": "Slot", + "name": "STARG2", + "tests": [ + { + "name": "Without slot", + "script": [ + "PUSH1", + "STARG2" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "PUSH0", + "INITSLOT", + "0x0001", + "PUSH1", + "STARG2" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "PUSH1", + "PUSH2", + "PUSH3", + "INITSLOT", + "0x0003", + "PUSH0", + "STARG2" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "arguments": [ + { + "type": "Integer", + "value": 1 + }, + { + "type": "Integer", + "value": 2 + }, + { + "type": "Integer", + "value": 0 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STARG3.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STARG3.json new file mode 100644 index 0000000000..e8ed64b00d --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STARG3.json @@ -0,0 +1,84 @@ +{ + "category": "Slot", + "name": "STARG3", + "tests": [ + { + "name": "Without slot", + "script": [ + "PUSH1", + "STARG3" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "PUSH0", + "INITSLOT", + "0x0001", + "PUSH1", + "STARG3" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "PUSH1", + "PUSH2", + "PUSH3", + "PUSH4", + "INITSLOT", + "0x0004", + "PUSH0", + "STARG3" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "arguments": [ + { + "type": "Integer", + "value": 1 + }, + { + "type": "Integer", + "value": 2 + }, + { + "type": "Integer", + "value": 3 + }, + { + "type": "Integer", + "value": 0 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STARG4.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STARG4.json new file mode 100644 index 0000000000..7b32e0e09a --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STARG4.json @@ -0,0 +1,89 @@ +{ + "category": "Slot", + "name": "STARG4", + "tests": [ + { + "name": "Without slot", + "script": [ + "PUSH1", + "STARG4" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "PUSH0", + "INITSLOT", + "0x0001", + "PUSH1", + "STARG4" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "PUSH1", + "PUSH2", + "PUSH3", + "PUSH4", + "PUSH5", + "INITSLOT", + "0x0005", + "PUSH0", + "STARG4" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "arguments": [ + { + "type": "Integer", + "value": 1 + }, + { + "type": "Integer", + "value": 2 + }, + { + "type": "Integer", + "value": 3 + }, + { + "type": "Integer", + "value": 4 + }, + { + "type": "Integer", + "value": 0 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STARG5.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STARG5.json new file mode 100644 index 0000000000..9bcef878b4 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STARG5.json @@ -0,0 +1,94 @@ +{ + "category": "Slot", + "name": "STARG5", + "tests": [ + { + "name": "Without slot", + "script": [ + "PUSH1", + "STARG5" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "PUSH0", + "INITSLOT", + "0x0001", + "PUSH1", + "STARG5" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "PUSH1", + "PUSH2", + "PUSH3", + "PUSH4", + "PUSH5", + "PUSH6", + "INITSLOT", + "0x0006", + "PUSH0", + "STARG5" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "arguments": [ + { + "type": "Integer", + "value": 1 + }, + { + "type": "Integer", + "value": 2 + }, + { + "type": "Integer", + "value": 3 + }, + { + "type": "Integer", + "value": 4 + }, + { + "type": "Integer", + "value": 5 + }, + { + "type": "Integer", + "value": 0 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STARG6.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STARG6.json new file mode 100644 index 0000000000..e8c0f09b9f --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STARG6.json @@ -0,0 +1,99 @@ +{ + "category": "Slot", + "name": "STARG6", + "tests": [ + { + "name": "Without slot", + "script": [ + "PUSH1", + "STARG6" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "PUSH0", + "INITSLOT", + "0x0001", + "PUSH1", + "STARG6" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "PUSH1", + "PUSH2", + "PUSH3", + "PUSH4", + "PUSH5", + "PUSH6", + "PUSH7", + "INITSLOT", + "0x0007", + "PUSH0", + "STARG6" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "arguments": [ + { + "type": "Integer", + "value": 1 + }, + { + "type": "Integer", + "value": 2 + }, + { + "type": "Integer", + "value": 3 + }, + { + "type": "Integer", + "value": 4 + }, + { + "type": "Integer", + "value": 5 + }, + { + "type": "Integer", + "value": 6 + }, + { + "type": "Integer", + "value": 0 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STLOC.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STLOC.json new file mode 100644 index 0000000000..aae5bbb9ea --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STLOC.json @@ -0,0 +1,92 @@ +{ + "category": "Slot", + "name": "STLOC", + "tests": [ + { + "name": "Without slot", + "script": [ + "STLOC", + "0x00" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without enough items", + "script": [ + "INITSLOT", + "0x0100", + "PUSH2", + "STLOC" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "INITSLOT", + "0x0100", + "PUSH1", + "STLOC", + "0x00", + "LDLOC", + "0x00" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "LDLOC", + "localVariables": [ + { + "type": "Integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STSFLD.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STSFLD.json new file mode 100644 index 0000000000..149244cbbd --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STSFLD.json @@ -0,0 +1,84 @@ +{ + "category": "Slot", + "name": "STSFLD", + "tests": [ + { + "name": "Without slot", + "script": [ + "STSFLD", + "0x00" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "INITSSLOT", + "0x01", + "STSFLD", + "0x01" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "INITSSLOT", + "0x01", + "PUSH1", + "STSFLD", + "0x00" + ], + "steps": [ + { + "actions": [ + "StepInto", + "StepInto", + "StepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET", + "staticFields": [ + { + "type": "Integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STSFLD0.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STSFLD0.json new file mode 100644 index 0000000000..d2ee516f22 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STSFLD0.json @@ -0,0 +1,63 @@ +{ + "category": "Slot", + "name": "STSFLD0", + "tests": [ + { + "name": "Without slot", + "script": [ + "STSFLD0" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "INITSSLOT", + "0x01", + "PUSH1", + "STSFLD0" + ], + "steps": [ + { + "actions": [ + "StepInto", + "StepInto", + "StepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "RET", + "staticFields": [ + { + "type": "Integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STSFLD1.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STSFLD1.json new file mode 100644 index 0000000000..5ba84602c1 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STSFLD1.json @@ -0,0 +1,84 @@ +{ + "category": "Slot", + "name": "STSFLD1", + "tests": [ + { + "name": "Without slot", + "script": [ + "STSFLD1" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "INITSSLOT", + "0x01", + "STSFLD1" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "INITSSLOT", + "0x02", + "PUSH1", + "STSFLD1" + ], + "steps": [ + { + "actions": [ + "StepInto", + "StepInto", + "StepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "RET", + "staticFields": [ + { + "type": "Null" + }, + { + "type": "Integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STSFLD2.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STSFLD2.json new file mode 100644 index 0000000000..d5c42ba76c --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STSFLD2.json @@ -0,0 +1,87 @@ +{ + "category": "Slot", + "name": "STSFLD2", + "tests": [ + { + "name": "Without slot", + "script": [ + "STSFLD2" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "INITSSLOT", + "0x01", + "STSFLD2" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "INITSSLOT", + "0x03", + "PUSH1", + "STSFLD2" + ], + "steps": [ + { + "actions": [ + "StepInto", + "StepInto", + "StepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "RET", + "staticFields": [ + { + "type": "Null" + }, + { + "type": "Null" + }, + { + "type": "Integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STSFLD3.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STSFLD3.json new file mode 100644 index 0000000000..f4ba41a606 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STSFLD3.json @@ -0,0 +1,90 @@ +{ + "category": "Slot", + "name": "STSFLD3", + "tests": [ + { + "name": "Without slot", + "script": [ + "STSFLD3" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "INITSSLOT", + "0x01", + "STSFLD3" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "INITSSLOT", + "0x04", + "PUSH1", + "STSFLD3" + ], + "steps": [ + { + "actions": [ + "StepInto", + "StepInto", + "StepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "RET", + "staticFields": [ + { + "type": "Null" + }, + { + "type": "Null" + }, + { + "type": "Null" + }, + { + "type": "Integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STSFLD4.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STSFLD4.json new file mode 100644 index 0000000000..48f4d09aa9 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STSFLD4.json @@ -0,0 +1,93 @@ +{ + "category": "Slot", + "name": "STSFLD4", + "tests": [ + { + "name": "Without slot", + "script": [ + "STSFLD4" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "INITSSLOT", + "0x01", + "STSFLD4" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "INITSSLOT", + "0x05", + "PUSH1", + "STSFLD4" + ], + "steps": [ + { + "actions": [ + "StepInto", + "StepInto", + "StepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "RET", + "staticFields": [ + { + "type": "Null" + }, + { + "type": "Null" + }, + { + "type": "Null" + }, + { + "type": "Null" + }, + { + "type": "Integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STSFLD5.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STSFLD5.json new file mode 100644 index 0000000000..f3d2573d63 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STSFLD5.json @@ -0,0 +1,96 @@ +{ + "category": "Slot", + "name": "STSFLD5", + "tests": [ + { + "name": "Without slot", + "script": [ + "STSFLD5" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "INITSSLOT", + "0x01", + "STSFLD5" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "INITSSLOT", + "0x06", + "PUSH1", + "STSFLD5" + ], + "steps": [ + { + "actions": [ + "StepInto", + "StepInto", + "StepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "RET", + "staticFields": [ + { + "type": "Null" + }, + { + "type": "Null" + }, + { + "type": "Null" + }, + { + "type": "Null" + }, + { + "type": "Null" + }, + { + "type": "Integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STSFLD6.json b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STSFLD6.json new file mode 100644 index 0000000000..2d89c71887 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Slot/STSFLD6.json @@ -0,0 +1,99 @@ +{ + "category": "Slot", + "name": "STSFLD6", + "tests": [ + { + "name": "Without slot", + "script": [ + "STSFLD6" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Index out of range", + "script": [ + "INITSSLOT", + "0x01", + "STSFLD6" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "INITSSLOT", + "0x07", + "PUSH1", + "STSFLD6" + ], + "steps": [ + { + "actions": [ + "StepInto", + "StepInto", + "StepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "RET", + "staticFields": [ + { + "type": "Null" + }, + { + "type": "Null" + }, + { + "type": "Null" + }, + { + "type": "Null" + }, + { + "type": "Null" + }, + { + "type": "Null" + }, + { + "type": "Integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Splice/CAT.json b/tests/Neo.VM.Tests/Tests/OpCodes/Splice/CAT.json new file mode 100644 index 0000000000..f7b6cf3430 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Splice/CAT.json @@ -0,0 +1,436 @@ +{ + "category": "Splice", + "name": "CAT", + "tests": [ + { + "name": "Max Item Length", + "script": [ + "INITSLOT", + "0x0200", + "PUSHINT32", + "0x00000100", + "STLOC0", + "PUSH1", + "STLOC1", + "PUSHDATA2", + "0x1000", + "0x000102030405060708090A0B0C0D0E0F", + "PUSHDATA2", + "0x1000", + "0x000102030405060708090A0B0C0D0E0F", + "CAT", + "LDLOC1", + "INC", + "STLOC1", + "LDLOC1", + "LDLOC0", + "LT", + "JMPIF_L", + "0xE6FFFFFF", + "PUSHDATA1", + "0x01", + "0x00", + "CAT" + ], + "steps": [ + { + "actions": ["execute"], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Wrong type [Map,ByteString]", + "script": [ + "PUSH0", + "NEWMAP", + "CAT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Wrong type [ByteString,Map]", + "script": [ + "NEWMAP", + "PUSH0", + "CAT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Wrong push", + "script": [ + "CAT" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "PUSHDATA1", + "0x01", + "0x01", + "PUSHDATA1", + "0x02", + "0x0203", + "CAT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 8, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Buffer", + "value": "0x010203" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Buffer", + "value": "0x010203" + } + ] + } + } + ] + }, + { + "name": "CAT int(0) with empty ByteString", + "script": [ + "PUSH1", + "DEC", + "PUSH0", + "CAT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "CAT", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Buffer", + "value": "" + } + ] + } + } + ] + }, + { + "name": "CAT 0x01 with empty ByteString", + "script": [ + "PUSH1", + "PUSH0", + "CAT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "CAT", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Buffer", + "value": "0x01" + } + ] + } + } + ] + }, + { + "name": "CAT empty ByteString with 0x01", + "script": [ + "PUSH0", + "PUSH1", + "CAT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "CAT", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Buffer", + "value": "0x01" + } + ] + } + } + ] + }, + { + "name": "CAT Buffers", + "script": [ + "PUSHDATA1", + "0x01AA", + "CONVERT", + "0x30", + "PUSHDATA1", + "0x01BB", + "CONVERT", + "0x30", + "INITSLOT", + "0x0002", + "LDARG1", + "LDARG0", + "CAT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 10, + "nextInstruction": "INITSLOT", + "evaluationStack": [ + { + "type": "Buffer", + "value": "0xBB" + }, + { + "type": "Buffer", + "value": "0xAA" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 13, + "nextInstruction": "LDARG1", + "arguments": [ + { + "type": "Buffer", + "value": "0xBB" + }, + { + "type": "Buffer", + "value": "0xAA" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 15, + "nextInstruction": "CAT", + "evaluationStack": [ + { + "type": "Buffer", + "value": "0xBB" + }, + { + "type": "Buffer", + "value": "0xAA" + } + ], + "arguments": [ + { + "type": "Buffer", + "value": "0xBB" + }, + { + "type": "Buffer", + "value": "0xAA" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 16, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Buffer", + "value": "0xAABB" + } + ], + "arguments": [ + { + "type": "Buffer", + "value": "0xBB" + }, + { + "type": "Buffer", + "value": "0xAA" + } + ] + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Splice/LEFT.json b/tests/Neo.VM.Tests/Tests/OpCodes/Splice/LEFT.json new file mode 100644 index 0000000000..02c0dbc0ce --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Splice/LEFT.json @@ -0,0 +1,228 @@ +{ + "category": "Splice", + "name": "LEFT", + "tests": [ + { + "name": "Without push", + "script": [ + "PUSH11", + "LEFT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Wrong type [Map]", + "script": [ + "PUSH4", + "NEWMAP", + "LEFT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "LEFT", + "evaluationStack": [ + { + "type": "map", + "value": {} + }, + { + "type": "integer", + "value": 4 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Negative value", + "script": [ + "PUSHDATA1", + "0x03", + "0x010203", + "PUSHM1", + "LEFT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "LEFT", + "evaluationStack": [ + { + "type": "integer", + "value": -1 + }, + { + "type": "ByteString", + "value": "0x010203" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Overflow string", + "script": [ + "PUSHDATA1", + "0x03", + "0x010203", + "PUSH4", + "LEFT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "LEFT", + "evaluationStack": [ + { + "type": "integer", + "value": 4 + }, + { + "type": "ByteString", + "value": "0x010203" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "PUSHDATA1", + "0x03", + "0x010203", + "PUSH2", + "LEFT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "LEFT", + "evaluationStack": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "ByteString", + "value": "0x010203" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 7, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Buffer", + "value": "0x0102" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Buffer", + "value": "0x0102" + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Splice/MEMCPY.json b/tests/Neo.VM.Tests/Tests/OpCodes/Splice/MEMCPY.json new file mode 100644 index 0000000000..fcff55af20 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Splice/MEMCPY.json @@ -0,0 +1,382 @@ +{ + "category": "Splice", + "name": "MEMCPY", + "tests": [ + { + "name": "Max Item Length", + "script": [ + "PUSH4", + "NEWBUFFER", + "PUSHINT32", + "0x00001000", + "PUSHDATA1", + "0x02", + "0x1111", + "PUSH0", + "PUSH2", + "MEMCPY" + ], + "steps": [ + { + "actions": [ + "StepInto", + "StepInto", + "StepInto", + "StepInto", + "StepInto", + "StepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 13, + "nextInstruction": "MEMCPY", + "evaluationStack": [ + { + "type": "Integer", + "value": 2 + }, + { + "type": "Integer", + "value": 0 + }, + { + "type": "ByteString", + "value": "0x1111" + }, + { + "type": "Integer", + "value": 1048576 + }, + { + "type": "Buffer", + "value": "0x00000000" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Negative di", + "script": [ + "PUSH4", + "NEWBUFFER", + "PUSHM1", + "PUSHDATA1", + "0x02", + "0x1111", + "PUSH0", + "PUSH2", + "MEMCPY" + ], + "steps": [ + { + "actions": [ + "StepInto", + "StepInto", + "StepInto", + "StepInto", + "StepInto", + "StepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 9, + "nextInstruction": "MEMCPY", + "evaluationStack": [ + { + "type": "Integer", + "value": 2 + }, + { + "type": "Integer", + "value": 0 + }, + { + "type": "ByteString", + "value": "0x1111" + }, + { + "type": "Integer", + "value": -1 + }, + { + "type": "Buffer", + "value": "0x00000000" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Negative si", + "script": [ + "PUSH4", + "NEWBUFFER", + "PUSHINT32", + "0x00001000", + "PUSHDATA1", + "0x02", + "0x1111", + "PUSHM1", + "PUSH2", + "MEMCPY" + ], + "steps": [ + { + "actions": [ + "StepInto", + "StepInto", + "StepInto", + "StepInto", + "StepInto", + "StepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 13, + "nextInstruction": "MEMCPY", + "evaluationStack": [ + { + "type": "Integer", + "value": 2 + }, + { + "type": "Integer", + "value": -1 + }, + { + "type": "ByteString", + "value": "0x1111" + }, + { + "type": "Integer", + "value": 1048576 + }, + { + "type": "Buffer", + "value": "0x00000000" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Negative n", + "script": [ + "PUSH4", + "NEWBUFFER", + "PUSHINT32", + "0x00001000", + "PUSHDATA1", + "0x02", + "0x1111", + "PUSH0", + "PUSHM1", + "MEMCPY" + ], + "steps": [ + { + "actions": [ + "StepInto", + "StepInto", + "StepInto", + "StepInto", + "StepInto", + "StepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 13, + "nextInstruction": "MEMCPY", + "evaluationStack": [ + { + "type": "Integer", + "value": -1 + }, + { + "type": "Integer", + "value": 0 + }, + { + "type": "ByteString", + "value": "0x1111" + }, + { + "type": "Integer", + "value": 1048576 + }, + { + "type": "Buffer", + "value": "0x00000000" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Wrong type [Array]", + "script": [ + "PUSH0", + "NEWARRAY", + "PUSHINT32", + "0x00001000", + "PUSHDATA1", + "0x02", + "0x1111", + "PUSH0", + "PUSH2", + "MEMCPY" + ], + "steps": [ + { + "actions": [ + "StepInto", + "StepInto", + "StepInto", + "StepInto", + "StepInto", + "StepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 13, + "nextInstruction": "MEMCPY", + "evaluationStack": [ + { + "type": "Integer", + "value": 2 + }, + { + "type": "Integer", + "value": 0 + }, + { + "type": "ByteString", + "value": "0x1111" + }, + { + "type": "Integer", + "value": 1048576 + }, + { + "type": "Array", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "No push", + "script": [ + "MEMCPY" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "PUSH4", + "NEWBUFFER", + "DUP", + "PUSH1", + "PUSHDATA1", + "0x02", + "0x1111", + "PUSH0", + "PUSH2", + "MEMCPY" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Buffer", + "value": "0x00111100" + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Splice/NEWBUFFER.json b/tests/Neo.VM.Tests/Tests/OpCodes/Splice/NEWBUFFER.json new file mode 100644 index 0000000000..f3c60bb802 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Splice/NEWBUFFER.json @@ -0,0 +1,159 @@ +{ + "category": "Splice", + "name": "NEWBUFFER", + "tests": [ + { + "name": "Max Item Length", + "script": [ + "PUSHINT256", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "NEWBUFFER" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 33, + "nextInstruction": "NEWBUFFER", + "evaluationStack": [ + { + "type": "Integer", + "value": "57896044618658097711785492504343953926634992332820282019728792003956564819967" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "-1 Length", + "script": [ + "PUSHM1", + "NEWBUFFER" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "NEWBUFFER", + "evaluationStack": [ + { + "type": "Integer", + "value": -1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Wrong type [Array]", + "script": [ + "PUSH0", + "NEWARRAY", + "NEWBUFFER" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "NEWBUFFER", + "evaluationStack": [ + { + "type": "Array", + "value": [] + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "No push", + "script": [ + "NEWBUFFER" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test [Integer]", + "script": [ + "PUSH10", + "NEWBUFFER" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Buffer", + "value": "0x00000000000000000000" + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Splice/RIGHT.json b/tests/Neo.VM.Tests/Tests/OpCodes/Splice/RIGHT.json new file mode 100644 index 0000000000..6c30ea8c91 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Splice/RIGHT.json @@ -0,0 +1,299 @@ +{ + "category": "Splice", + "name": "RIGHT", + "tests": [ + { + "name": "Without push", + "script": [ + "PUSH11", + "RIGHT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Wrong type [Map]", + "script": [ + "PUSH4", + "NEWMAP", + "RIGHT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "RIGHT", + "evaluationStack": [ + { + "type": "map", + "value": {} + }, + { + "type": "integer", + "value": 4 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Negative value", + "script": [ + "PUSHDATA1", + "0x03", + "0x010203", + "PUSHM1", + "RIGHT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "RIGHT", + "evaluationStack": [ + { + "type": "integer", + "value": -1 + }, + { + "type": "ByteString", + "value": "0x010203" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Overflow string", + "script": [ + "PUSHDATA1", + "0x03", + "0x010203", + "PUSH4", + "RIGHT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "RIGHT", + "evaluationStack": [ + { + "type": "integer", + "value": 4 + }, + { + "type": "ByteString", + "value": "0x010203" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "PUSHDATA1", + "0x03", + "0x010203", + "PUSH2", + "RIGHT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "RIGHT", + "evaluationStack": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "ByteString", + "value": "0x010203" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 7, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Buffer", + "value": "0x0203" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Buffer", + "value": "0x0203" + } + ] + } + } + ] + }, + { + "name": "Real test [whole input]", + "script": [ + "PUSHDATA1", + "0x03", + "0x010203", + "PUSH3", + "RIGHT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "RIGHT", + "evaluationStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "ByteString", + "value": "0x010203" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 7, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Buffer", + "value": "0x010203" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Buffer", + "value": "0x010203" + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Splice/SUBSTR.json b/tests/Neo.VM.Tests/Tests/OpCodes/Splice/SUBSTR.json new file mode 100644 index 0000000000..7b78d7e053 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Splice/SUBSTR.json @@ -0,0 +1,478 @@ +{ + "category": "Splice", + "name": "SUBSTR", + "tests": [ + { + "name": "Without 3 items", + "script": [ + "PUSH2", + "PUSH3", + "SUBSTR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "SUBSTR", + "evaluationStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "With negative count", + "script": [ + "PUSH0", + "PUSH0", + "PUSHM1", + "SUBSTR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "SUBSTR", + "evaluationStack": [ + { + "type": "integer", + "value": -1 + }, + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "With map as string", + "script": [ + "NEWMAP", + "PUSH0", + "PUSH0", + "SUBSTR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "SUBSTR", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 0 + }, + { + "type": "map", + "value": {} + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "With map as count", + "script": [ + "PUSH0", + "PUSH0", + "NEWMAP", + "SUBSTR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "SUBSTR", + "evaluationStack": [ + { + "type": "map", + "value": {} + }, + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "With map as index", + "script": [ + "PUSH0", + "NEWMAP", + "PUSH0", + "SUBSTR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "SUBSTR", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "map", + "value": {} + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "With negative index", + "script": [ + "PUSH0", + "PUSHM1", + "PUSH0", + "SUBSTR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "SUBSTR", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": -1 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Overflow string index", + "script": [ + "PUSHDATA1", + "0x02", + "0x0001", + "PUSH9", + "PUSH2", + "SUBSTR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "SUBSTR", + "evaluationStack": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 9 + }, + { + "type": "ByteString", + "value": "0x0001" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Overflow string count", + "script": [ + "PUSHDATA1", + "0x0a", + "0x00010203040506070809", + "PUSH2", + "PUSH9", + "SUBSTR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 14, + "nextInstruction": "SUBSTR", + "evaluationStack": [ + { + "type": "integer", + "value": 9 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "ByteString", + "value": "0x00010203040506070809" + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "PUSHDATA1", + "0x0a", + "0x00010203040506070809", + "PUSH2", + "PUSH1", + "SUBSTR" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 14, + "nextInstruction": "SUBSTR", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "ByteString", + "value": "0x00010203040506070809" + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Buffer", + "value": "0x02" + } + ] + } + } + ] + }, + { + "name": "Integer overflow Test", + "script": [ + "PUSHDATA1", + "0xff", + "0x414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141", + "PUSHDATA1", + "0x04", + "0xfd000000", + "PUSHDATA1", + "0x04", + "0x03ffff7f", + "SUBSTR" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Stack/CLEAR.json b/tests/Neo.VM.Tests/Tests/OpCodes/Stack/CLEAR.json new file mode 100644 index 0000000000..36e9fcdd09 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Stack/CLEAR.json @@ -0,0 +1,59 @@ +{ + "category": "Stack", + "name": "CLEAR", + "tests": [ + { + "name": "Without push", + "script": [ + "CLEAR" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "With push", + "script": [ + "PUSH2", + "CLEAR" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "CLEAR", + "evaluationStack": [ + { + "type": "integer", + "value": 2 + } + ] + } + ] + } + }, + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Stack/DEPTH.json b/tests/Neo.VM.Tests/Tests/OpCodes/Stack/DEPTH.json new file mode 100644 index 0000000000..9a2ed2f774 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Stack/DEPTH.json @@ -0,0 +1,223 @@ +{ + "category": "Stack", + "name": "DEPTH", + "tests": [ + { + "name": "Without push", + "script": [ + "DEPTH" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + } + ] + }, + { + "name": "With push", + "script": [ + "PUSH2", + "DEPTH" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "DEPTH", + "evaluationStack": [ + { + "type": "integer", + "value": 2 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 2 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 2 + } + ] + } + } + ] + }, + { + "name": "3xDEPTH", + "script": [ + "DEPTH", + "DEPTH", + "DEPTH" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "DEPTH", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "DEPTH", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 0 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Stack/DROP.json b/tests/Neo.VM.Tests/Tests/OpCodes/Stack/DROP.json new file mode 100644 index 0000000000..804a710958 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Stack/DROP.json @@ -0,0 +1,73 @@ +{ + "category": "Stack", + "name": "DROP", + "tests": [ + { + "name": "Without push", + "script": [ + "DROP" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without push", + "script": [ + "PUSH5", + "DROP" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "DROP", + "evaluationStack": [ + { + "type": "integer", + "value": 5 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Stack/NIP.json b/tests/Neo.VM.Tests/Tests/OpCodes/Stack/NIP.json new file mode 100644 index 0000000000..e363d56444 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Stack/NIP.json @@ -0,0 +1,132 @@ +{ + "category": "Stack", + "name": "NIP", + "tests": [ + { + "name": "Without push", + "script": [ + "NIP" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without two stack items", + "script": [ + "PUSH5", + "NIP" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "NIP", + "evaluationStack": [ + { + "type": "integer", + "value": 5 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "PUSH0", + "PUSHDATA1", + "0x09", + "0x000000000000000000", + "NOT", + "NIP" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 13, + "nextInstruction": "NIP", + "evaluationStack": [ + { + "type": "Boolean", + "value": true + }, + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 14, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Stack/OVER.json b/tests/Neo.VM.Tests/Tests/OpCodes/Stack/OVER.json new file mode 100644 index 0000000000..03ec0d16ee --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Stack/OVER.json @@ -0,0 +1,243 @@ +{ + "category": "Stack", + "name": "OVER", + "tests": [ + { + "name": "Without push", + "script": [ + "OVER" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "With one push", + "script": [ + "PUSH0", + "OVER" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "OVER", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test with 2 items", + "script": [ + "PUSH1", + "PUSH2", + "OVER" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "OVER", + "evaluationStack": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + } + ] + }, + { + "name": "Real test with 3 items", + "script": [ + "PUSH1", + "PUSH2", + "PUSH3", + "OVER" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "OVER", + "evaluationStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Stack/PICK.json b/tests/Neo.VM.Tests/Tests/OpCodes/Stack/PICK.json new file mode 100644 index 0000000000..5791acec70 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Stack/PICK.json @@ -0,0 +1,397 @@ +{ + "category": "Stack", + "name": "PICK", + "tests": [ + { + "name": "Without push", + "script": [ + "PICK" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Pick outside", + "script": [ + "PUSH1", + "PUSH2", + "PUSH3", + "PUSH4", + "PICK" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "PICK", + "evaluationStack": [ + { + "type": "integer", + "value": 4 + }, + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Less than 0", + "script": [ + "PUSH1", + "PUSH2", + "PUSH3", + "PUSHM1", + "PICK" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "PICK", + "evaluationStack": [ + { + "type": "integer", + "value": -1 + }, + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Wrong type", + "script": [ + "PUSH1", + "PUSH2", + "PUSH3", + "NEWMAP", + "PICK" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "PICK", + "evaluationStack": [ + { + "type": "map", + "value": {} + }, + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "PUSH3", + "PUSH2", + "PUSH1", + "PUSH2", + "PICK" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "PICK", + "evaluationStack": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 3 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 3 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 3 + } + ] + } + } + ] + }, + { + "name": "Real test with 0", + "script": [ + "PUSH3", + "PUSH2", + "PUSH1", + "PUSH0", + "PICK" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "PICK", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 3 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 3 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 3 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Stack/REVERSE3.json b/tests/Neo.VM.Tests/Tests/OpCodes/Stack/REVERSE3.json new file mode 100644 index 0000000000..1260a70d68 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Stack/REVERSE3.json @@ -0,0 +1,85 @@ +{ + "category": "Stack", + "name": "REVERSE3", + "tests": [ + { + "name": "Without push", + "script": [ + "REVERSE3" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "With push", + "script": [ + "PUSH1", + "PUSH2", + "PUSH3", + "REVERSE3" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "REVERSE3", + "evaluationStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 3 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Stack/REVERSE4.json b/tests/Neo.VM.Tests/Tests/OpCodes/Stack/REVERSE4.json new file mode 100644 index 0000000000..d3e04e437e --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Stack/REVERSE4.json @@ -0,0 +1,95 @@ +{ + "category": "Stack", + "name": "REVERSE4", + "tests": [ + { + "name": "Without push", + "script": [ + "REVERSE4" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "With push", + "script": [ + "PUSH1", + "PUSH2", + "PUSH3", + "PUSH4", + "REVERSE4" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "REVERSE4", + "evaluationStack": [ + { + "type": "integer", + "value": 4 + }, + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 4 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Stack/REVERSEN.json b/tests/Neo.VM.Tests/Tests/OpCodes/Stack/REVERSEN.json new file mode 100644 index 0000000000..e9a1518826 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Stack/REVERSEN.json @@ -0,0 +1,201 @@ +{ + "category": "Stack", + "name": "REVERSEN", + "tests": [ + { + "name": "Without push", + "script": [ + "REVERSEN" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "N = -1", + "script": [ + "PUSH1", + "PUSHM1", + "REVERSEN" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "N < DEPTH", + "script": [ + "PUSH1", + "REVERSEN" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT", + "resultStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + } + ] + }, + { + "name": "Reverse 0 item", + "script": [ + "PUSH1", + "PUSH2", + "PUSH3", + "PUSH0", + "REVERSEN" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "REVERSEN", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + } + ] + }, + { + "name": "Reverse 3 items", + "script": [ + "PUSH1", + "PUSH2", + "PUSH3", + "PUSH3", + "REVERSEN" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "REVERSEN", + "evaluationStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 3 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Stack/ROLL.json b/tests/Neo.VM.Tests/Tests/OpCodes/Stack/ROLL.json new file mode 100644 index 0000000000..882c577d4a --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Stack/ROLL.json @@ -0,0 +1,398 @@ +{ + "category": "Stack", + "name": "ROLL", + "tests": [ + { + "name": "Without push", + "script": [ + "ROLL" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "With -1", + "script": [ + "PUSHM1", + "ROLL" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Pick outside", + "script": [ + "PUSH1", + "PUSH2", + "PUSH3", + "PUSH4", + "ROLL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "ROLL", + "evaluationStack": [ + { + "type": "integer", + "value": 4 + }, + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Less than 0", + "script": [ + "PUSH1", + "PUSH2", + "PUSH3", + "PUSHM1", + "ROLL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "ROLL", + "evaluationStack": [ + { + "type": "integer", + "value": -1 + }, + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "With 0", + "script": [ + "PUSH1", + "PUSH2", + "PUSH3", + "PUSH0", + "ROLL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "ROLL", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + }, + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + } + ] + }, + { + "name": "Wrong type", + "script": [ + "PUSH1", + "PUSH2", + "PUSH3", + "NEWMAP", + "ROLL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "ROLL", + "evaluationStack": [ + { + "type": "map", + "value": {} + }, + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "PUSH3", + "PUSH2", + "PUSH1", + "PUSH2", + "ROLL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "ROLL", + "evaluationStack": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 3 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 2 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 2 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Stack/ROT.json b/tests/Neo.VM.Tests/Tests/OpCodes/Stack/ROT.json new file mode 100644 index 0000000000..48c8771af3 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Stack/ROT.json @@ -0,0 +1,193 @@ +{ + "category": "Stack", + "name": "ROT", + "tests": [ + { + "name": "Without push", + "script": [ + "ROT" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "With one push", + "script": [ + "PUSH0", + "ROT" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "ROT", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "With 2 items", + "script": [ + "PUSH1", + "PUSH2", + "ROT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "ROT", + "evaluationStack": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test with 3 items", + "script": [ + "PUSH1", + "PUSH2", + "PUSH3", + "ROT" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "ROT", + "evaluationStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Stack/SWAP.json b/tests/Neo.VM.Tests/Tests/OpCodes/Stack/SWAP.json new file mode 100644 index 0000000000..c5e93a9b59 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Stack/SWAP.json @@ -0,0 +1,209 @@ +{ + "category": "Stack", + "name": "SWAP", + "tests": [ + { + "name": "Without push", + "script": [ + "SWAP" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "With one push", + "script": [ + "PUSH0", + "SWAP" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "SWAP", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test with 2 items", + "script": [ + "PUSH1", + "PUSH2", + "SWAP" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "SWAP", + "evaluationStack": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 2 + } + ] + } + ] + } + } + ] + }, + { + "name": "Real test with 3 items", + "script": [ + "PUSH1", + "PUSH2", + "PUSH3", + "SWAP" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "SWAP", + "evaluationStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Stack/TUCK.json b/tests/Neo.VM.Tests/Tests/OpCodes/Stack/TUCK.json new file mode 100644 index 0000000000..031855f31f --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Stack/TUCK.json @@ -0,0 +1,243 @@ +{ + "category": "Stack", + "name": "TUCK", + "tests": [ + { + "name": "Without push", + "script": [ + "TUCK" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Outside", + "script": [ + "PUSH0", + "TUCK" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "TUCK", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test - Last item", + "script": [ + "PUSH1", + "PUSH2", + "TUCK" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "TUCK", + "evaluationStack": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 2 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 2 + } + ] + } + } + ] + }, + { + "name": "Real test", + "script": [ + "PUSH1", + "PUSH2", + "PUSH3", + "TUCK" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "TUCK", + "evaluationStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Stack/XDROP.json b/tests/Neo.VM.Tests/Tests/OpCodes/Stack/XDROP.json new file mode 100644 index 0000000000..8666fed600 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Stack/XDROP.json @@ -0,0 +1,238 @@ +{ + "category": "Stack", + "name": "XDROP", + "tests": [ + { + "name": "Without push", + "script": [ + "XDROP" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "With -1", + "script": [ + "PUSHM1", + "XDROP" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Overflow drop", + "script": [ + "PUSH3", + "PUSH2", + "PUSH1", + "PUSH3", + "XDROP" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "XDROP", + "evaluationStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 3 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Wrong index type [Map]", + "script": [ + "PUSH3", + "PUSH2", + "PUSH1", + "NEWMAP", + "XDROP" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "XDROP", + "evaluationStack": [ + { + "type": "map", + "value": {} + }, + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 3 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Real test", + "script": [ + "PUSH3", + "PUSH2", + "PUSH1", + "PUSH1", + "XDROP" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "XDROP", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 3 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 3 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 1 + }, + { + "type": "integer", + "value": 3 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Types/CONVERT.json b/tests/Neo.VM.Tests/Tests/OpCodes/Types/CONVERT.json new file mode 100644 index 0000000000..473ed8bba4 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Types/CONVERT.json @@ -0,0 +1,898 @@ +{ + "category": "Types", + "name": "CONVERT", + "tests": [ + { + "name": "Null to Buffer", + "script": [ + "PUSHNULL", + "CONVERT", + "0x30" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "CONVERT", + "evaluationStack": [ + { + "type": "null" + } + ] + } + ] + } + }, + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "null" + } + ] + } + } + ] + }, + { + "name": "Null to Boolean", + "script": [ + "PUSHNULL", + "CONVERT", + "0x20" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "CONVERT", + "evaluationStack": [ + { + "type": "null" + } + ] + } + ] + } + }, + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "null" + } + ] + } + } + ] + }, + { + "name": "Struct to Array", + "script": [ + "PUSH1", + "NEWSTRUCT", + "DUP", + "PUSH0", + "PUSH5", + "SETITEM", + "CONVERT", + "0x40" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "CONVERT", + "evaluationStack": [ + { + "type": "struct", + "value": [ + { + "type": "Integer", + "value": 5 + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 5 + } + ] + } + ] + } + } + ] + }, + { + "name": "Struct to Boolean", + "script": [ + "PUSH0", + "NEWSTRUCT", + "CONVERT", + "0x20" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "CONVERT", + "evaluationStack": [ + { + "type": "struct", + "value": [ + ] + } + ] + } + ] + } + }, + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Array to Boolean", + "script": [ + "PUSH0", + "NEWARRAY", + "CONVERT", + "0x20" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "CONVERT", + "evaluationStack": [ + { + "type": "array", + "value": [ + ] + } + ] + } + ] + } + }, + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Array to Struct", + "script": [ + "PUSH1", + "NEWARRAY", + "DUP", + "PUSH0", + "PUSH5", + "SETITEM", + "CONVERT", + "0x41" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "CONVERT", + "evaluationStack": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 5 + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "struct", + "value": [ + { + "type": "Integer", + "value": 5 + } + ] + } + ] + } + } + ] + }, + { + "name": "Array to Integer", + "script": [ + "PUSH1", + "NEWARRAY", + "DUP", + "PUSH0", + "PUSH4", + "SETITEM", + "CONVERT", + "0x21" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "CONVERT", + "evaluationStack": [ + { + "type": "array", + "value": [ + { + "type": "Integer", + "value": 4 + } + ] + } + ] + } + ] + } + }, + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Integer to ByteString", + "script": [ + "PUSHINT8", + "0x0A", + "CONVERT", + "0x28" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "ByteString", + "value": "0x0A" + } + ] + } + } + ] + }, + { + "name": "Integer to Boolean", + "script": [ + "PUSHINT8", + "0x0A", + "CONVERT", + "0x20" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Integer to Boolean", + "script": [ + "PUSHINT8", + "0x00", + "CONVERT", + "0x20" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Integer to Boolean", + "script": [ + "PUSHINT8", + "0x01", + "CONVERT", + "0x20" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Integer to Buffer", + "script": [ + "PUSHINT8", + "0x0A", + "CONVERT", + "0x30" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Buffer", + "value": "0x0A" + } + ] + } + } + ] + }, + { + "name": "ByteString to Buffer", + "script": [ + "PUSHDATA1", + "0x0A", + "0x00000000000000000000", + "CONVERT", + "0x30" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Buffer", + "value": "0x00000000000000000000" + } + ] + } + } + ] + }, + { + "name": "ByteString to Integer", + "script": [ + "PUSHDATA1", + "0x02", + "0x0A0B", + "CONVERT", + "0x21" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 2826 + } + ] + } + } + ] + }, + { + "name": "ByteString to Boolean", + "script": [ + "PUSHDATA1", + "0x02", + "0x0A0B", + "CONVERT", + "0x20" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "ByteString to Boolean", + "script": [ + "PUSHDATA1", + "0x02", + "0x0000", + "CONVERT", + "0x20" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "ByteString to Boolean", + "script": [ + "PUSHDATA1", + "0x02", + "0x0001", + "CONVERT", + "0x20" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "ByteString to Boolean", + "script": [ + "PUSHDATA1", + "0x01", + "0x01", + "CONVERT", + "0x20" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Buffer to ByteString", + "script": [ + "PUSHDATA1", + "0x0A", + "0x00000000000000000000", + "CONVERT", + "0x30", + "CONVERT", + "0x28" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "ByteString", + "value": "0x00000000000000000000" + } + ] + } + } + ] + }, + { + "name": "Buffer to Boolean", + "script": [ + "PUSHDATA1", + "0x01", + "0x00", + "CONVERT", + "0x30", + "CONVERT", + "0x20" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Buffer to Boolean", + "script": [ + "PUSHDATA1", + "0x01", + "0x02", + "CONVERT", + "0x30", + "CONVERT", + "0x20" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Buffer to Integer", + "script": [ + "PUSHDATA1", + "0x02", + "0x0102", + "CONVERT", + "0x30", + "CONVERT", + "0x21" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Integer", + "value": 513 + } + ] + } + } + ] + }, + { + "name": "Buffer to Integer (Exceed)", + "script": [ + "PUSHDATA1", + "0x21", + "0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "CONVERT", + "0x30", + "CONVERT", + "0x21" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Buffer to Any", + "script": [ + "PUSHDATA1", + "0x02", + "0x0102", + "CONVERT", + "0x30", + "CONVERT", + "0x00" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Buffer to Interop", + "script": [ + "PUSHDATA1", + "0x02", + "0x0102", + "CONVERT", + "0x30", + "CONVERT", + "0x60" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "ByteString to non-defined", + "script": [ + "PUSHDATA1", + "0x01", + "0xAA", + "CONVERT", + "0xFF" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Array to non-defined", + "script": [ + "NEWARRAY0", + "CONVERT", + "0xFF" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Struct to non-defined", + "script": [ + "NEWSTRUCT0", + "CONVERT", + "0xFF" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Null to non-defined", + "script": [ + "PUSHNULL", + "CONVERT", + "0xFF" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Types/ISNULL.json b/tests/Neo.VM.Tests/Tests/OpCodes/Types/ISNULL.json new file mode 100644 index 0000000000..b87fa84726 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Types/ISNULL.json @@ -0,0 +1,147 @@ +{ + "category": "Types", + "name": "ISNULL", + "tests": [ + { + "name": "Without push", + "script": [ + "ISNULL" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Good definition", + "script": [ + "PUSHNULL", + "ISNULL" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "ISNULL", + "evaluationStack": [ + { + "type": "null" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "With empty ByteString", + "script": [ + "PUSH0", + "ISNULL" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "ISNULL", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Types/ISTYPE.json b/tests/Neo.VM.Tests/Tests/OpCodes/Types/ISTYPE.json new file mode 100644 index 0000000000..6bdbd7495c --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Types/ISTYPE.json @@ -0,0 +1,226 @@ +{ + "category": "Types", + "name": "ISTYPE", + "tests": [ + { + "name": "Array", + "script": [ + "NEWARRAY0", + "ISTYPE", + "0x40" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Buffer", + "script": [ + "PUSH0", + "NEWBUFFER", + "ISTYPE", + "0x30" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "ByteString", + "script": [ + "PUSHDATA1", + "0x00", + "ISTYPE", + "0x28" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Integer", + "script": [ + "PUSH0", + "ISTYPE", + "0x21" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "InteropInterface", + "script": [ + "SYSCALL", + "0x77777777", + "ISTYPE", + "0x60" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Map", + "script": [ + "NEWMAP", + "ISTYPE", + "0x48" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Null", + "script": [ + "PUSHNULL", + "ISTYPE", + "0x20" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + } + ] + }, + { + "name": "Pointer", + "script": [ + "PUSHA", + "0x00000000", + "ISTYPE", + "0x10" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + }, + { + "name": "Struct", + "script": [ + "NEWSTRUCT0", + "ISTYPE", + "0x41" + ], + "steps": [ + { + "actions": [ + "Execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/Others/Debugger.json b/tests/Neo.VM.Tests/Tests/Others/Debugger.json new file mode 100644 index 0000000000..7dd1096d55 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/Others/Debugger.json @@ -0,0 +1,426 @@ +{ + "category": "Others", + "name": "Debugger", + "tests": [ + { + "name": "Step Into", + "script": [ + "PUSH1", + "CALL", + "0x04", + "PUSH3", + "RET", + "PUSH2", + "RET" + ], + "steps": [ + { + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 0, + "nextInstruction": "PUSH1" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "CALL", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "PUSH2", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + }, + { + "instructionPointer": 3, + "nextInstruction": "PUSH3", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 6, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + }, + { + "instructionPointer": 3, + "nextInstruction": "PUSH3", + "evaluationStack": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "PUSH3", + "evaluationStack": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + } + ] + }, + { + "name": "Step Out", + "script": [ + "PUSH1", + "CALL", + "0x04", + "PUSH3", + "RET", + "PUSH2", + "RET" + ], + "steps": [ + { + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 0, + "nextInstruction": "PUSH1" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "CALL", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepOut" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + } + ] + }, + { + "name": "Step Over", + "script": [ + "PUSH1", + "CALL", + "0x04", + "PUSH3", + "RET", + "PUSH2", + "RET" + ], + "steps": [ + { + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 0, + "nextInstruction": "PUSH1" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "CALL", + "evaluationStack": [ + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepOver" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "PUSH3", + "evaluationStack": [ + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepOver" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "RET", + "evaluationStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + ] + } + }, + { + "actions": [ + "stepOver" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + } + ] + }, + { + "name": "Execute", + "script": [ + "PUSH1", + "CALL", + "0x04", + "PUSH3", + "RET", + "PUSH2", + "RET" + ], + "steps": [ + { + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 0, + "nextInstruction": "PUSH1" + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "resultStack": [ + { + "type": "integer", + "value": 3 + }, + { + "type": "integer", + "value": 2 + }, + { + "type": "integer", + "value": 1 + } + ] + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/Others/Init.json b/tests/Neo.VM.Tests/Tests/Others/Init.json new file mode 100644 index 0000000000..ab95168cde --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/Others/Init.json @@ -0,0 +1,41 @@ +{ + "category": "Others", + "name": "Init", + "tests": [ + { + "name": "Init script", + "script": [ + "RET" + ], + "steps": [ + { + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 0, + "nextInstruction": "RET" + } + ] + } + } + ] + }, + { + "name": "Init script", + "script": [ + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/Others/InvocationLimits.json b/tests/Neo.VM.Tests/Tests/Others/InvocationLimits.json new file mode 100644 index 0000000000..5939dabf80 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/Others/InvocationLimits.json @@ -0,0 +1,120 @@ +{ + "category": "Limits", + "name": "Invocation limits", + "tests": [ + { + "name": "More than 1024 ExecutionContext", + "script": [ + "INITSSLOT", + "0x01", + "PUSHDATA1", + "0x02", + "0x0004", + "INC", + "STSFLD0", + "LDSFLD0", + "DEC", + "DUP", + "STSFLD0", + "JMPIFNOT", + "0x04", + "CALL", + "0xfa", + "RET" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 8, + "nextInstruction": "LDSFLD0", + "staticFields": [ + { + "type": "integer", + "value": 1025 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 12, + "nextInstruction": "JMPIFNOT", + "evaluationStack": [ + { + "type": "integer", + "value": 1024 + } + ], + "staticFields": [ + { + "type": "integer", + "value": 1024 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 8, + "nextInstruction": "LDSFLD0", + "staticFields": [ + { + "type": "integer", + "value": 1024 + } + ] + }, + { + "instructionPointer": 16, + "nextInstruction": "RET", + "staticFields": [ + { + "type": "integer", + "value": 1024 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/Others/OtherCases.json b/tests/Neo.VM.Tests/Tests/Others/OtherCases.json new file mode 100644 index 0000000000..693bf7cba7 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/Others/OtherCases.json @@ -0,0 +1,36 @@ +{ + "category": "Limits", + "name": "OtherCases", + "tests": [ + { + "name": "Wrong script", + "script": [ + "0xff" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Without script", + "script": [], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/Others/ScriptLogic.json b/tests/Neo.VM.Tests/Tests/Others/ScriptLogic.json new file mode 100644 index 0000000000..a49be7a8bf --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/Others/ScriptLogic.json @@ -0,0 +1,99 @@ +{ + "category": "Others", + "name": "ScriptLogic", + "tests": [ + { + "name": "Script logic", + "script": [ + "PUSH0", + "NOT", + "NOT", + "DROP" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 1, + "nextInstruction": "NOT", + "evaluationStack": [ + { + "type": "integer", + "value": 0 + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 2, + "nextInstruction": "NOT", + "evaluationStack": [ + { + "type": "Boolean", + "value": true + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 3, + "nextInstruction": "DROP", + "evaluationStack": [ + { + "type": "Boolean", + "value": false + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "RET" + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "HALT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/Others/StackItemLimits.json b/tests/Neo.VM.Tests/Tests/Others/StackItemLimits.json new file mode 100644 index 0000000000..77293f9f89 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/Others/StackItemLimits.json @@ -0,0 +1,71 @@ +{ + "category": "Limits", + "name": "Stack item limits [StackItemLimits] [StackItemLimits] [StackItemLimits]", + "tests": [ + { + "name": "Max boolean ByteString", + "script": [ + "PUSHDATA1", + "0x21", + "0x000000000000000000000000000000000000000000000000000000000000000000", + "NOT" + ], + "steps": [ + { + "actions": [ + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 35, + "nextInstruction": "NOT", + "evaluationStack": [ + { + "type": "ByteString", + "value": "0x000000000000000000000000000000000000000000000000000000000000000000" + } + ] + } + ] + } + }, + { + "actions": [ + "stepInto" + ], + "result": { + "state": "FAULT" + } + } + ] + }, + { + "name": "Max items with PUSHDATA (2048+1)", + "script": [ + "PUSHINT16", + "0x0004", + "NEWARRAY", + "UNPACK", + "PUSHINT16", + "0xfe03", + "NEWARRAY", + "UNPACK", + "PUSHDATA1", + "0x01", + "0x01" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Tests/Others/StackLimits.json b/tests/Neo.VM.Tests/Tests/Others/StackLimits.json new file mode 100644 index 0000000000..8191112a26 --- /dev/null +++ b/tests/Neo.VM.Tests/Tests/Others/StackLimits.json @@ -0,0 +1,6181 @@ +{ + "category": "Limits", + "name": "Stack limits [StackLimits] [StackLimits] [StackLimits]", + "tests": [ + { + "name": "Good: 2048 PUSH1 + 2048 DROP", + "script": [ + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP", + "DROP" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT" + } + } + ] + }, + { + "name": "Bad: 2049 PUSH1", + "script": [ + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1", + "PUSH1" + ], + "steps": [ + { + "actions": [ + "execute" + ], + "result": { + "state": "FAULT" + } + } + ] + } + ] +} diff --git a/tests/Neo.VM.Tests/Types/TestEngine.cs b/tests/Neo.VM.Tests/Types/TestEngine.cs new file mode 100644 index 0000000000..cf99314892 --- /dev/null +++ b/tests/Neo.VM.Tests/Types/TestEngine.cs @@ -0,0 +1,56 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TestEngine.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.VM; +using Neo.VM.Types; +using System; + +namespace Neo.Test.Types +{ + class TestEngine : ExecutionEngine + { + public Exception FaultException { get; private set; } + + public TestEngine() : base(ComposeJumpTable()) { } + + private static JumpTable ComposeJumpTable() + { + JumpTable jumpTable = new JumpTable(); + jumpTable[OpCode.SYSCALL] = OnSysCall; + return jumpTable; + } + + private static void OnSysCall(ExecutionEngine engine, Instruction instruction) + { + uint method = instruction.TokenU32; + + if (method == 0x77777777) + { + engine.CurrentContext.EvaluationStack.Push(StackItem.FromInterface(new object())); + return; + } + + if (method == 0xaddeadde) + { + engine.JumpTable.ExecuteThrow(engine, "error"); + return; + } + + throw new Exception(); + } + + protected override void OnFault(Exception ex) + { + FaultException = ex; + base.OnFault(ex); + } + } +} diff --git a/tests/Neo.VM.Tests/Types/VMUT.cs b/tests/Neo.VM.Tests/Types/VMUT.cs new file mode 100644 index 0000000000..b24255d372 --- /dev/null +++ b/tests/Neo.VM.Tests/Types/VMUT.cs @@ -0,0 +1,27 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// VMUT.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Newtonsoft.Json; + +namespace Neo.Test.Types +{ + public class VMUT + { + [JsonProperty] + public string Category { get; set; } + + [JsonProperty] + public string Name { get; set; } + + [JsonProperty] + public VMUTEntry[] Tests { get; set; } + } +} diff --git a/tests/Neo.VM.Tests/Types/VMUTActionType.cs b/tests/Neo.VM.Tests/Types/VMUTActionType.cs new file mode 100644 index 0000000000..5ac8b5b48e --- /dev/null +++ b/tests/Neo.VM.Tests/Types/VMUTActionType.cs @@ -0,0 +1,24 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// VMUTActionType.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.Test.Types +{ + public enum VMUTActionType + { + Execute, + + // Steps + + StepInto, + StepOut, + StepOver, + } +} diff --git a/tests/Neo.VM.Tests/Types/VMUTEntry.cs b/tests/Neo.VM.Tests/Types/VMUTEntry.cs new file mode 100644 index 0000000000..7761b08de5 --- /dev/null +++ b/tests/Neo.VM.Tests/Types/VMUTEntry.cs @@ -0,0 +1,28 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// VMUTEntry.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Test.Converters; +using Newtonsoft.Json; + +namespace Neo.Test.Types +{ + public class VMUTEntry + { + [JsonProperty(Order = 1)] + public string Name { get; set; } + + [JsonProperty(Order = 2), JsonConverter(typeof(ScriptConverter))] + public byte[] Script { get; set; } + + [JsonProperty(Order = 3)] + public VMUTStep[] Steps { get; set; } + } +} diff --git a/tests/Neo.VM.Tests/Types/VMUTExecutionContextState.cs b/tests/Neo.VM.Tests/Types/VMUTExecutionContextState.cs new file mode 100644 index 0000000000..101142dc3a --- /dev/null +++ b/tests/Neo.VM.Tests/Types/VMUTExecutionContextState.cs @@ -0,0 +1,42 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// VMUTExecutionContextState.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Test.Converters; +using Neo.VM; +using Newtonsoft.Json; + +namespace Neo.Test.Types +{ + public class VMUTExecutionContextState + { + [JsonProperty] + public int InstructionPointer { get; set; } + + [JsonProperty, JsonConverter(typeof(UppercaseEnum))] + public OpCode NextInstruction { get; set; } + + // Stacks + + [JsonProperty] + public VMUTStackItem[] EvaluationStack { get; set; } + + // Slots + + [JsonProperty] + public VMUTStackItem[] StaticFields { get; set; } + + [JsonProperty] + public VMUTStackItem[] Arguments { get; set; } + + [JsonProperty] + public VMUTStackItem[] LocalVariables { get; set; } + } +} diff --git a/tests/Neo.VM.Tests/Types/VMUTExecutionEngineState.cs b/tests/Neo.VM.Tests/Types/VMUTExecutionEngineState.cs new file mode 100644 index 0000000000..3f931b3c32 --- /dev/null +++ b/tests/Neo.VM.Tests/Types/VMUTExecutionEngineState.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// VMUTExecutionEngineState.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Test.Converters; +using Neo.VM; +using Newtonsoft.Json; + +namespace Neo.Test.Types +{ + public class VMUTExecutionEngineState + { + [JsonProperty, JsonConverter(typeof(UppercaseEnum))] + public VMState State { get; set; } + + [JsonProperty] + public VMUTStackItem[] ResultStack { get; set; } + + [JsonProperty] + public VMUTExecutionContextState[] InvocationStack { get; set; } + + [JsonProperty] + public string ExceptionMessage { get; set; } + } +} diff --git a/tests/Neo.VM.Tests/Types/VMUTStackItem.cs b/tests/Neo.VM.Tests/Types/VMUTStackItem.cs new file mode 100644 index 0000000000..5ce55bbfd3 --- /dev/null +++ b/tests/Neo.VM.Tests/Types/VMUTStackItem.cs @@ -0,0 +1,25 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// VMUTStackItem.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Neo.Test.Types +{ + public class VMUTStackItem + { + [JsonProperty] + public VMUTStackItemType Type { get; set; } + + [JsonProperty] + public JToken Value { get; set; } + } +} diff --git a/tests/Neo.VM.Tests/Types/VMUTStackItemType.cs b/tests/Neo.VM.Tests/Types/VMUTStackItemType.cs new file mode 100644 index 0000000000..845c062a02 --- /dev/null +++ b/tests/Neo.VM.Tests/Types/VMUTStackItemType.cs @@ -0,0 +1,71 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// VMUTStackItemType.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.Test.Types +{ + public enum VMUTStackItemType + { + /// + /// Null + /// + Null, + + /// + /// An address of function + /// + Pointer, + + /// + /// Boolean (true,false) + /// + Boolean, + + /// + /// ByteString + /// + ByteString, + + /// + /// ByteString as UTF8 string + /// + String, + + /// + /// Mutable byte array + /// + Buffer, + + /// + /// InteropInterface + /// + Interop, + + /// + /// BigInteger + /// + Integer, + + /// + /// Array + /// + Array, + + /// + /// Struct + /// + Struct, + + /// + /// Map + /// + Map + } +} diff --git a/tests/Neo.VM.Tests/Types/VMUTStep.cs b/tests/Neo.VM.Tests/Types/VMUTStep.cs new file mode 100644 index 0000000000..ad49606d4b --- /dev/null +++ b/tests/Neo.VM.Tests/Types/VMUTStep.cs @@ -0,0 +1,27 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// VMUTStep.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Newtonsoft.Json; + +namespace Neo.Test.Types +{ + public class VMUTStep + { + [JsonProperty] + public string Name { get; set; } + + [JsonProperty] + public VMUTActionType[] Actions { get; set; } + + [JsonProperty] + public VMUTExecutionEngineState Result { get; set; } + } +} diff --git a/tests/Neo.VM.Tests/UT_Debugger.cs b/tests/Neo.VM.Tests/UT_Debugger.cs new file mode 100644 index 0000000000..64f543f65e --- /dev/null +++ b/tests/Neo.VM.Tests/UT_Debugger.cs @@ -0,0 +1,218 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Debugger.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.VM; + +namespace Neo.Test +{ + [TestClass] + public class UT_Debugger + { + [TestMethod] + public void TestBreakPoint() + { + using ExecutionEngine engine = new(); + using ScriptBuilder script = new(); + script.Emit(OpCode.NOP); + script.Emit(OpCode.NOP); + script.Emit(OpCode.NOP); + script.Emit(OpCode.NOP); + + engine.LoadScript(script.ToArray()); + + Debugger debugger = new(engine); + + Assert.IsFalse(debugger.RemoveBreakPoint(engine.CurrentContext.Script, 3)); + + Assert.AreEqual(OpCode.NOP, engine.CurrentContext.NextInstruction.OpCode); + + debugger.AddBreakPoint(engine.CurrentContext.Script, 2); + debugger.AddBreakPoint(engine.CurrentContext.Script, 3); + debugger.Execute(); + Assert.AreEqual(OpCode.NOP, engine.CurrentContext.NextInstruction.OpCode); + Assert.AreEqual(2, engine.CurrentContext.InstructionPointer); + Assert.AreEqual(VMState.BREAK, engine.State); + + Assert.IsTrue(debugger.RemoveBreakPoint(engine.CurrentContext.Script, 2)); + Assert.IsFalse(debugger.RemoveBreakPoint(engine.CurrentContext.Script, 2)); + Assert.IsTrue(debugger.RemoveBreakPoint(engine.CurrentContext.Script, 3)); + Assert.IsFalse(debugger.RemoveBreakPoint(engine.CurrentContext.Script, 3)); + debugger.Execute(); + Assert.AreEqual(VMState.HALT, engine.State); + } + + [TestMethod] + public void TestWithoutBreakPoints() + { + using ExecutionEngine engine = new(); + using ScriptBuilder script = new(); + script.Emit(OpCode.NOP); + script.Emit(OpCode.NOP); + script.Emit(OpCode.NOP); + script.Emit(OpCode.NOP); + + engine.LoadScript(script.ToArray()); + + Debugger debugger = new(engine); + + Assert.AreEqual(OpCode.NOP, engine.CurrentContext.NextInstruction.OpCode); + + debugger.Execute(); + + Assert.IsNull(engine.CurrentContext); + Assert.AreEqual(VMState.HALT, engine.State); + } + + [TestMethod] + public void TestWithoutDebugger() + { + using ExecutionEngine engine = new(); + using ScriptBuilder script = new(); + script.Emit(OpCode.NOP); + script.Emit(OpCode.NOP); + script.Emit(OpCode.NOP); + script.Emit(OpCode.NOP); + + engine.LoadScript(script.ToArray()); + + Assert.AreEqual(OpCode.NOP, engine.CurrentContext.NextInstruction.OpCode); + + engine.Execute(); + + Assert.IsNull(engine.CurrentContext); + Assert.AreEqual(VMState.HALT, engine.State); + } + + [TestMethod] + public void TestStepOver() + { + using ExecutionEngine engine = new(); + using ScriptBuilder script = new(); + /* ┌ CALL + │ ┌> NOT + │ │ RET + └> │ PUSH0 + └─┘ RET */ + script.EmitCall(4); + script.Emit(OpCode.NOT); + script.Emit(OpCode.RET); + script.Emit(OpCode.PUSH0); + script.Emit(OpCode.RET); + + engine.LoadScript(script.ToArray()); + + Debugger debugger = new(engine); + + Assert.AreEqual(OpCode.NOT, engine.CurrentContext.NextInstruction.OpCode); + Assert.AreEqual(VMState.BREAK, debugger.StepOver()); + Assert.AreEqual(2, engine.CurrentContext.InstructionPointer); + Assert.AreEqual(VMState.BREAK, engine.State); + Assert.AreEqual(OpCode.RET, engine.CurrentContext.NextInstruction.OpCode); + + debugger.Execute(); + + Assert.AreEqual(true, engine.ResultStack.Pop().GetBoolean()); + Assert.AreEqual(VMState.HALT, engine.State); + + // Test step over again + + Assert.AreEqual(VMState.HALT, debugger.StepOver()); + Assert.AreEqual(VMState.HALT, engine.State); + } + + [TestMethod] + public void TestStepInto() + { + using ExecutionEngine engine = new(); + using ScriptBuilder script = new(); + /* ┌ CALL + │ ┌> NOT + │ │ RET + └> │ PUSH0 + └─┘ RET */ + script.EmitCall(4); + script.Emit(OpCode.NOT); + script.Emit(OpCode.RET); + script.Emit(OpCode.PUSH0); + script.Emit(OpCode.RET); + + engine.LoadScript(script.ToArray()); + + Debugger debugger = new(engine); + + var context = engine.CurrentContext; + + Assert.AreEqual(context, engine.CurrentContext); + Assert.AreEqual(context, engine.EntryContext); + Assert.AreEqual(OpCode.NOT, engine.CurrentContext.NextInstruction.OpCode); + + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + + Assert.AreNotEqual(context, engine.CurrentContext); + Assert.AreEqual(context, engine.EntryContext); + Assert.AreEqual(OpCode.RET, engine.CurrentContext.NextInstruction.OpCode); + + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + + Assert.AreEqual(context, engine.CurrentContext); + Assert.AreEqual(context, engine.EntryContext); + Assert.AreEqual(OpCode.RET, engine.CurrentContext.NextInstruction.OpCode); + + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(VMState.HALT, debugger.StepInto()); + + Assert.AreEqual(true, engine.ResultStack.Pop().GetBoolean()); + Assert.AreEqual(VMState.HALT, engine.State); + + // Test step into again + + Assert.AreEqual(VMState.HALT, debugger.StepInto()); + Assert.AreEqual(VMState.HALT, engine.State); + } + + [TestMethod] + public void TestBreakPointStepOver() + { + using ExecutionEngine engine = new(); + using ScriptBuilder script = new(); + /* ┌ CALL + │ ┌> NOT + │ │ RET + └>X│ PUSH0 + └┘ RET */ + script.EmitCall(4); + script.Emit(OpCode.NOT); + script.Emit(OpCode.RET); + script.Emit(OpCode.PUSH0); + script.Emit(OpCode.RET); + + engine.LoadScript(script.ToArray()); + + Debugger debugger = new(engine); + + Assert.AreEqual(OpCode.NOT, engine.CurrentContext.NextInstruction.OpCode); + + debugger.AddBreakPoint(engine.CurrentContext.Script, 5); + Assert.AreEqual(VMState.BREAK, debugger.StepOver()); + + Assert.IsNull(engine.CurrentContext.NextInstruction); + Assert.AreEqual(5, engine.CurrentContext.InstructionPointer); + Assert.AreEqual(VMState.BREAK, engine.State); + + debugger.Execute(); + + Assert.AreEqual(true, engine.ResultStack.Pop().GetBoolean()); + Assert.AreEqual(VMState.HALT, engine.State); + } + } +} diff --git a/tests/Neo.VM.Tests/UT_EvaluationStack.cs b/tests/Neo.VM.Tests/UT_EvaluationStack.cs new file mode 100644 index 0000000000..fb1f5e5813 --- /dev/null +++ b/tests/Neo.VM.Tests/UT_EvaluationStack.cs @@ -0,0 +1,224 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_EvaluationStack.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Test.Extensions; +using Neo.VM; +using Neo.VM.Types; +using System; +using System.Collections; +using System.Linq; + +namespace Neo.Test +{ + [TestClass] + public class UT_EvaluationStack + { + private static EvaluationStack CreateOrderedStack(int count) + { + var check = new Integer[count]; + var stack = new EvaluationStack(new ReferenceCounter()); + + for (int x = 1; x <= count; x++) + { + stack.Push(x); + check[x - 1] = x; + } + + Assert.AreEqual(count, stack.Count); + CollectionAssert.AreEqual(check, stack.ToArray()); + + return stack; + } + + public static IEnumerable GetEnumerable(IEnumerator enumerator) + { + while (enumerator.MoveNext()) yield return enumerator.Current; + } + + [TestMethod] + public void TestClear() + { + var stack = CreateOrderedStack(3); + stack.Clear(); + Assert.AreEqual(0, stack.Count); + } + + [TestMethod] + public void TestCopyTo() + { + var stack = CreateOrderedStack(3); + var copy = new EvaluationStack(new ReferenceCounter()); + + Assert.ThrowsException(() => stack.CopyTo(copy, -2)); + Assert.ThrowsException(() => stack.CopyTo(copy, 4)); + + stack.CopyTo(copy, 0); + + Assert.AreEqual(3, stack.Count); + Assert.AreEqual(0, copy.Count); + CollectionAssert.AreEqual(new Integer[] { 1, 2, 3 }, stack.ToArray()); + + stack.CopyTo(copy, -1); + + Assert.AreEqual(3, stack.Count); + Assert.AreEqual(3, copy.Count); + CollectionAssert.AreEqual(new Integer[] { 1, 2, 3 }, stack.ToArray()); + + // Test IEnumerable + + var enumerable = (IEnumerable)copy; + var enumerator = enumerable.GetEnumerator(); + + CollectionAssert.AreEqual(new Integer[] { 1, 2, 3 }, GetEnumerable(enumerator).Cast().ToArray()); + + copy.CopyTo(stack, 2); + + Assert.AreEqual(5, stack.Count); + Assert.AreEqual(3, copy.Count); + + CollectionAssert.AreEqual(new Integer[] { 1, 2, 3, 2, 3 }, stack.ToArray()); + CollectionAssert.AreEqual(new Integer[] { 1, 2, 3 }, copy.ToArray()); + } + + [TestMethod] + public void TestMoveTo() + { + var stack = CreateOrderedStack(3); + var other = new EvaluationStack(new ReferenceCounter()); + + stack.MoveTo(other, 0); + + Assert.AreEqual(3, stack.Count); + Assert.AreEqual(0, other.Count); + CollectionAssert.AreEqual(new Integer[] { 1, 2, 3 }, stack.ToArray()); + + stack.MoveTo(other, -1); + + Assert.AreEqual(0, stack.Count); + Assert.AreEqual(3, other.Count); + CollectionAssert.AreEqual(new Integer[] { 1, 2, 3 }, other.ToArray()); + + // Test IEnumerable + + var enumerable = (IEnumerable)other; + var enumerator = enumerable.GetEnumerator(); + + CollectionAssert.AreEqual(new Integer[] { 1, 2, 3 }, GetEnumerable(enumerator).Cast().ToArray()); + + other.MoveTo(stack, 2); + + Assert.AreEqual(2, stack.Count); + Assert.AreEqual(1, other.Count); + + CollectionAssert.AreEqual(new Integer[] { 2, 3 }, stack.ToArray()); + CollectionAssert.AreEqual(new Integer[] { 1 }, other.ToArray()); + } + + [TestMethod] + public void TestInsertPeek() + { + var stack = new EvaluationStack(new ReferenceCounter()); + + stack.Insert(0, 3); + stack.Insert(1, 1); + stack.Insert(1, 2); + + Assert.ThrowsException(() => stack.Insert(4, 2)); + + Assert.AreEqual(3, stack.Count); + CollectionAssert.AreEqual(new Integer[] { 1, 2, 3 }, stack.ToArray()); + + Assert.AreEqual(3, stack.Peek(0)); + Assert.AreEqual(2, stack.Peek(1)); + Assert.AreEqual(1, stack.Peek(-1)); + + Assert.ThrowsException(() => stack.Peek(-4)); + } + + [TestMethod] + public void TestPopPush() + { + var stack = CreateOrderedStack(3); + + Assert.AreEqual(3, stack.Pop()); + Assert.AreEqual(2, stack.Pop()); + Assert.AreEqual(1, stack.Pop()); + + Assert.ThrowsException(() => stack.Pop()); + + stack = CreateOrderedStack(3); + + Assert.IsTrue(stack.Pop().Equals(3)); + Assert.IsTrue(stack.Pop().Equals(2)); + Assert.IsTrue(stack.Pop().Equals(1)); + + Assert.ThrowsException(() => stack.Pop()); + } + + [TestMethod] + public void TestRemove() + { + var stack = CreateOrderedStack(3); + + Assert.IsTrue(stack.Remove(0).Equals(3)); + Assert.IsTrue(stack.Remove(0).Equals(2)); + Assert.IsTrue(stack.Remove(-1).Equals(1)); + + Assert.ThrowsException(() => stack.Remove(0)); + Assert.ThrowsException(() => stack.Remove(-1)); + } + + [TestMethod] + public void TestReverse() + { + var stack = CreateOrderedStack(3); + + stack.Reverse(3); + Assert.IsTrue(stack.Pop().Equals(1)); + Assert.IsTrue(stack.Pop().Equals(2)); + Assert.IsTrue(stack.Pop().Equals(3)); + Assert.ThrowsException(() => stack.Pop().Equals(0)); + + stack = CreateOrderedStack(3); + + Assert.ThrowsException(() => stack.Reverse(-1)); + Assert.ThrowsException(() => stack.Reverse(4)); + + stack.Reverse(1); + Assert.IsTrue(stack.Pop().Equals(3)); + Assert.IsTrue(stack.Pop().Equals(2)); + Assert.IsTrue(stack.Pop().Equals(1)); + Assert.ThrowsException(() => stack.Pop().Equals(0)); + } + + [TestMethod] + public void TestEvaluationStackPrint() + { + var stack = new EvaluationStack(new ReferenceCounter()); + + stack.Insert(0, 3); + stack.Insert(1, 1); + stack.Insert(2, "test"); + stack.Insert(3, true); + + Assert.AreEqual("[Boolean(True), ByteString(\"test\"), Integer(1), Integer(3)]", stack.ToString()); + } + + [TestMethod] + public void TestPrintInvalidUTF8() + { + var stack = new EvaluationStack(new ReferenceCounter()); + stack.Insert(0, "4CC95219999D421243C8161E3FC0F4290C067845".FromHexString()); + Assert.AreEqual("[ByteString(\"Base64: TMlSGZmdQhJDyBYeP8D0KQwGeEU=\")]", stack.ToString()); + } + } +} diff --git a/tests/Neo.VM.Tests/UT_ExecutionContext.cs b/tests/Neo.VM.Tests/UT_ExecutionContext.cs new file mode 100644 index 0000000000..b53003a7e6 --- /dev/null +++ b/tests/Neo.VM.Tests/UT_ExecutionContext.cs @@ -0,0 +1,66 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ExecutionContext.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.VM; +using System; +using System.Collections.Generic; + +namespace Neo.Test +{ + [TestClass] + public class UT_ExecutionContext + { + class TestState + { + public bool Flag = false; + } + + [TestMethod] + public void TestStateTest() + { + var context = new ExecutionContext(Array.Empty(), -1, new ReferenceCounter()); + + // Test factory + + var flag = context.GetState(() => new TestState() { Flag = true }); + Assert.IsTrue(flag.Flag); + + flag.Flag = false; + + flag = context.GetState(() => new TestState() { Flag = true }); + Assert.IsFalse(flag.Flag); + + // Test new + + var stack = context.GetState>(); + Assert.AreEqual(0, stack.Count); + stack.Push(100); + stack = context.GetState>(); + Assert.AreEqual(100, stack.Pop()); + stack.Push(100); + + // Test clone + + var copy = context.Clone(); + var copyStack = copy.GetState>(); + Assert.AreEqual(1, copyStack.Count); + copyStack.Push(200); + copyStack = context.GetState>(); + Assert.AreEqual(200, copyStack.Pop()); + Assert.AreEqual(100, copyStack.Pop()); + copyStack.Push(200); + + stack = context.GetState>(); + Assert.AreEqual(200, stack.Pop()); + } + } +} diff --git a/tests/Neo.VM.Tests/UT_ReferenceCounter.cs b/tests/Neo.VM.Tests/UT_ReferenceCounter.cs new file mode 100644 index 0000000000..f974670805 --- /dev/null +++ b/tests/Neo.VM.Tests/UT_ReferenceCounter.cs @@ -0,0 +1,244 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ReferenceCounter.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.VM; +using Neo.VM.Types; + +namespace Neo.Test +{ + [TestClass] + public class UT_ReferenceCounter + { + [TestMethod] + public void TestCircularReferences() + { + using ScriptBuilder sb = new(); + sb.Emit(OpCode.INITSSLOT, new byte[] { 1 }); //{}|{null}:1 + sb.EmitPush(0); //{0}|{null}:2 + sb.Emit(OpCode.NEWARRAY); //{A[]}|{null}:2 + sb.Emit(OpCode.DUP); //{A[],A[]}|{null}:3 + sb.Emit(OpCode.DUP); //{A[],A[],A[]}|{null}:4 + sb.Emit(OpCode.APPEND); //{A[A]}|{null}:3 + sb.Emit(OpCode.DUP); //{A[A],A[A]}|{null}:4 + sb.EmitPush(0); //{A[A],A[A],0}|{null}:5 + sb.Emit(OpCode.NEWARRAY); //{A[A],A[A],B[]}|{null}:5 + sb.Emit(OpCode.STSFLD0); //{A[A],A[A]}|{B[]}:4 + sb.Emit(OpCode.LDSFLD0); //{A[A],A[A],B[]}|{B[]}:5 + sb.Emit(OpCode.APPEND); //{A[A,B]}|{B[]}:4 + sb.Emit(OpCode.LDSFLD0); //{A[A,B],B[]}|{B[]}:5 + sb.EmitPush(0); //{A[A,B],B[],0}|{B[]}:6 + sb.Emit(OpCode.NEWARRAY); //{A[A,B],B[],C[]}|{B[]}:6 + sb.Emit(OpCode.TUCK); //{A[A,B],C[],B[],C[]}|{B[]}:7 + sb.Emit(OpCode.APPEND); //{A[A,B],C[]}|{B[C]}:6 + sb.EmitPush(0); //{A[A,B],C[],0}|{B[C]}:7 + sb.Emit(OpCode.NEWARRAY); //{A[A,B],C[],D[]}|{B[C]}:7 + sb.Emit(OpCode.TUCK); //{A[A,B],D[],C[],D[]}|{B[C]}:8 + sb.Emit(OpCode.APPEND); //{A[A,B],D[]}|{B[C[D]]}:7 + sb.Emit(OpCode.LDSFLD0); //{A[A,B],D[],B[C]}|{B[C[D]]}:8 + sb.Emit(OpCode.APPEND); //{A[A,B]}|{B[C[D[B]]]}:7 + sb.Emit(OpCode.PUSHNULL); //{A[A,B],null}|{B[C[D[B]]]}:8 + sb.Emit(OpCode.STSFLD0); //{A[A,B[C[D[B]]]]}|{null}:7 + sb.Emit(OpCode.DUP); //{A[A,B[C[D[B]]]],A[A,B]}|{null}:8 + sb.EmitPush(1); //{A[A,B[C[D[B]]]],A[A,B],1}|{null}:9 + sb.Emit(OpCode.REMOVE); //{A[A]}|{null}:3 + sb.Emit(OpCode.STSFLD0); //{}|{A[A]}:2 + sb.Emit(OpCode.RET); //{}:0 + + using ExecutionEngine engine = new(); + Debugger debugger = new(engine); + engine.LoadScript(sb.ToArray()); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(1, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(2, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(2, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(3, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(4, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(3, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(4, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(5, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(5, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(4, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(5, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(4, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(5, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(6, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(6, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(7, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(6, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(7, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(7, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(8, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(7, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(8, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(7, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(8, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(7, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(8, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(9, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(6, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(5, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.HALT, debugger.Execute()); + Assert.AreEqual(4, engine.ReferenceCounter.Count); + } + + [TestMethod] + public void TestRemoveReferrer() + { + using ScriptBuilder sb = new(); + sb.Emit(OpCode.INITSSLOT, new byte[] { 1 }); //{}|{null}:1 + sb.EmitPush(0); //{0}|{null}:2 + sb.Emit(OpCode.NEWARRAY); //{A[]}|{null}:2 + sb.Emit(OpCode.DUP); //{A[],A[]}|{null}:3 + sb.EmitPush(0); //{A[],A[],0}|{null}:4 + sb.Emit(OpCode.NEWARRAY); //{A[],A[],B[]}|{null}:4 + sb.Emit(OpCode.STSFLD0); //{A[],A[]}|{B[]}:3 + sb.Emit(OpCode.LDSFLD0); //{A[],A[],B[]}|{B[]}:4 + sb.Emit(OpCode.APPEND); //{A[B]}|{B[]}:3 + sb.Emit(OpCode.DROP); //{}|{B[]}:1 + sb.Emit(OpCode.RET); //{}:0 + + using ExecutionEngine engine = new(); + Debugger debugger = new(engine); + engine.LoadScript(sb.ToArray()); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(1, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(2, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(2, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(3, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(4, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(4, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(3, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(4, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(3, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.BREAK, debugger.StepInto()); + Assert.AreEqual(2, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.HALT, debugger.Execute()); + Assert.AreEqual(1, engine.ReferenceCounter.Count); + } + + [TestMethod] + public void TestCheckZeroReferredWithArray() + { + using ScriptBuilder sb = new(); + + sb.EmitPush(ExecutionEngineLimits.Default.MaxStackSize - 1); + sb.Emit(OpCode.NEWARRAY); + + // Good with MaxStackSize + + using (ExecutionEngine engine = new()) + { + engine.LoadScript(sb.ToArray()); + Assert.AreEqual(0, engine.ReferenceCounter.Count); + + Assert.AreEqual(VMState.HALT, engine.Execute()); + Assert.AreEqual((int)ExecutionEngineLimits.Default.MaxStackSize, engine.ReferenceCounter.Count); + } + + // Fault with MaxStackSize+1 + + sb.Emit(OpCode.PUSH1); + + using (ExecutionEngine engine = new()) + { + engine.LoadScript(sb.ToArray()); + Assert.AreEqual(0, engine.ReferenceCounter.Count); + + Assert.AreEqual(VMState.FAULT, engine.Execute()); + Assert.AreEqual((int)ExecutionEngineLimits.Default.MaxStackSize + 1, engine.ReferenceCounter.Count); + } + } + + [TestMethod] + public void TestCheckZeroReferred() + { + using ScriptBuilder sb = new(); + + for (int x = 0; x < ExecutionEngineLimits.Default.MaxStackSize; x++) + sb.Emit(OpCode.PUSH1); + + // Good with MaxStackSize + + using (ExecutionEngine engine = new()) + { + engine.LoadScript(sb.ToArray()); + Assert.AreEqual(0, engine.ReferenceCounter.Count); + + Assert.AreEqual(VMState.HALT, engine.Execute()); + Assert.AreEqual((int)ExecutionEngineLimits.Default.MaxStackSize, engine.ReferenceCounter.Count); + } + + // Fault with MaxStackSize+1 + + sb.Emit(OpCode.PUSH1); + + using (ExecutionEngine engine = new()) + { + engine.LoadScript(sb.ToArray()); + Assert.AreEqual(0, engine.ReferenceCounter.Count); + + Assert.AreEqual(VMState.FAULT, engine.Execute()); + Assert.AreEqual((int)ExecutionEngineLimits.Default.MaxStackSize + 1, engine.ReferenceCounter.Count); + } + } + + [TestMethod] + public void TestArrayNoPush() + { + using ScriptBuilder sb = new(); + sb.Emit(OpCode.RET); + using ExecutionEngine engine = new(); + engine.LoadScript(sb.ToArray()); + Assert.AreEqual(0, engine.ReferenceCounter.Count); + Array array = new(engine.ReferenceCounter, new StackItem[] { 1, 2, 3, 4 }); + Assert.AreEqual(array.Count, engine.ReferenceCounter.Count); + Assert.AreEqual(VMState.HALT, engine.Execute()); + Assert.AreEqual(array.Count, engine.ReferenceCounter.Count); + } + } +} diff --git a/tests/Neo.VM.Tests/UT_Script.cs b/tests/Neo.VM.Tests/UT_Script.cs new file mode 100644 index 0000000000..c703d2685f --- /dev/null +++ b/tests/Neo.VM.Tests/UT_Script.cs @@ -0,0 +1,104 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Script.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.VM; +using System; +using System.Text; + +namespace Neo.Test +{ + [TestClass] + public class UT_Script + { + [TestMethod] + public void TestConversion() + { + byte[] rawScript; + using (var builder = new ScriptBuilder()) + { + builder.Emit(OpCode.PUSH0); + builder.Emit(OpCode.CALL, new byte[] { 0x00, 0x01 }); + builder.EmitSysCall(123); + + rawScript = builder.ToArray(); + } + + var script = new Script(rawScript); + + ReadOnlyMemory scriptConversion = script; + Assert.AreEqual(rawScript, scriptConversion); + } + + [TestMethod] + public void TestStrictMode() + { + var rawScript = new byte[] { (byte)OpCode.PUSH0, 0xFF }; + Assert.ThrowsException(() => new Script(rawScript, true)); + + var script = new Script(rawScript, false); + Assert.AreEqual(2, script.Length); + + rawScript = new byte[] { (byte)OpCode.PUSHDATA1 }; + Assert.ThrowsException(() => new Script(rawScript, true)); + + rawScript = new byte[] { (byte)OpCode.PUSHDATA2 }; + Assert.ThrowsException(() => new Script(rawScript, true)); + + rawScript = new byte[] { (byte)OpCode.PUSHDATA4 }; + Assert.ThrowsException(() => new Script(rawScript, true)); + } + + [TestMethod] + public void TestParse() + { + Script script; + + using (var builder = new ScriptBuilder()) + { + builder.Emit(OpCode.PUSH0); + builder.Emit(OpCode.CALL_L, new byte[] { 0x00, 0x01, 0x00, 0x00 }); + builder.EmitSysCall(123); + + script = new Script(builder.ToArray()); + } + + Assert.AreEqual(11, script.Length); + + var ins = script.GetInstruction(0); + + Assert.AreEqual(OpCode.PUSH0, ins.OpCode); + Assert.IsTrue(ins.Operand.IsEmpty); + Assert.AreEqual(1, ins.Size); + Assert.ThrowsException(() => { var x = ins.TokenI16; }); + Assert.ThrowsException(() => { var x = ins.TokenU32; }); + + ins = script.GetInstruction(1); + + Assert.AreEqual(OpCode.CALL_L, ins.OpCode); + CollectionAssert.AreEqual(new byte[] { 0x00, 0x01, 0x00, 0x00 }, ins.Operand.ToArray()); + Assert.AreEqual(5, ins.Size); + Assert.AreEqual(256, ins.TokenI32); + Assert.AreEqual(Encoding.ASCII.GetString(new byte[] { 0x00, 0x01, 0x00, 0x00 }), ins.TokenString); + + ins = script.GetInstruction(6); + + Assert.AreEqual(OpCode.SYSCALL, ins.OpCode); + CollectionAssert.AreEqual(new byte[] { 123, 0x00, 0x00, 0x00 }, ins.Operand.ToArray()); + Assert.AreEqual(5, ins.Size); + Assert.AreEqual(123, ins.TokenI16); + Assert.AreEqual(Encoding.ASCII.GetString(new byte[] { 123, 0x00, 0x00, 0x00 }), ins.TokenString); + Assert.AreEqual(123U, ins.TokenU32); + + Assert.ThrowsException(() => script.GetInstruction(100)); + } + } +} diff --git a/tests/Neo.VM.Tests/UT_ScriptBuilder.cs b/tests/Neo.VM.Tests/UT_ScriptBuilder.cs new file mode 100644 index 0000000000..faa57f3345 --- /dev/null +++ b/tests/Neo.VM.Tests/UT_ScriptBuilder.cs @@ -0,0 +1,275 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ScriptBuilder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Test.Extensions; +using Neo.Test.Helpers; +using Neo.VM; +using System; +using System.Linq; +using System.Numerics; +using System.Text; + +namespace Neo.Test +{ + [TestClass] + public class UT_ScriptBuilder + { + [TestMethod] + public void TestEmit() + { + using (ScriptBuilder script = new()) + { + Assert.AreEqual(0, script.Length); + script.Emit(OpCode.NOP); + Assert.AreEqual(1, script.Length); + + CollectionAssert.AreEqual(new byte[] { 0x21 }, script.ToArray()); + } + + using (ScriptBuilder script = new()) + { + script.Emit(OpCode.NOP, new byte[] { 0x66 }); + CollectionAssert.AreEqual(new byte[] { 0x21, 0x66 }, script.ToArray()); + } + } + + [TestMethod] + public void TestBigInteger() + { + using (ScriptBuilder script = new()) + { + Assert.AreEqual(0, script.Length); + script.EmitPush(-100000); + Assert.AreEqual(5, script.Length); + + CollectionAssert.AreEqual(new byte[] { 2, 96, 121, 254, 255 }, script.ToArray()); + } + + using (ScriptBuilder script = new()) + { + Assert.AreEqual(0, script.Length); + script.EmitPush(100000); + Assert.AreEqual(5, script.Length); + + CollectionAssert.AreEqual(new byte[] { 2, 160, 134, 1, 0 }, script.ToArray()); + } + } + + [TestMethod] + public void TestEmitSysCall() + { + using ScriptBuilder script = new(); + script.EmitSysCall(0xE393C875); + CollectionAssert.AreEqual(new byte[] { (byte)OpCode.SYSCALL, 0x75, 0xC8, 0x93, 0xE3 }.ToArray(), script.ToArray()); + } + + [TestMethod] + public void TestEmitCall() + { + using (ScriptBuilder script = new()) + { + script.EmitCall(0); + CollectionAssert.AreEqual(new[] { (byte)OpCode.CALL, (byte)0 }, script.ToArray()); + } + using (ScriptBuilder script = new()) + { + script.EmitCall(12345); + CollectionAssert.AreEqual(new[] { (byte)OpCode.CALL_L }.Concat(BitConverter.GetBytes(12345)).ToArray(), script.ToArray()); + } + using (ScriptBuilder script = new()) + { + script.EmitCall(-12345); + CollectionAssert.AreEqual(new[] { (byte)OpCode.CALL_L }.Concat(BitConverter.GetBytes(-12345)).ToArray(), script.ToArray()); + } + } + + [TestMethod] + public void TestEmitJump() + { + var offset_i8 = sbyte.MaxValue; + var offset_i32 = int.MaxValue; + + foreach (OpCode op in Enum.GetValues(typeof(OpCode))) + { + using ScriptBuilder script = new(); + if (op < OpCode.JMP || op > OpCode.JMPLE_L) + { + Assert.ThrowsException(() => script.EmitJump(op, offset_i8)); + Assert.ThrowsException(() => script.EmitJump(op, offset_i32)); + } + else + { + script.EmitJump(op, offset_i8); + script.EmitJump(op, offset_i32); + if ((int)op % 2 == 0) + CollectionAssert.AreEqual(new[] { (byte)op, (byte)offset_i8, (byte)(op + 1) }.Concat(BitConverter.GetBytes(offset_i32)).ToArray(), script.ToArray()); + else + CollectionAssert.AreEqual(new[] { (byte)op }.Concat(BitConverter.GetBytes((int)offset_i8)).Concat(new[] { (byte)op }).Concat(BitConverter.GetBytes(offset_i32)).ToArray(), script.ToArray()); + } + } + + offset_i8 = sbyte.MinValue; + offset_i32 = int.MinValue; + + foreach (OpCode op in Enum.GetValues(typeof(OpCode))) + { + using ScriptBuilder script = new(); + if (op < OpCode.JMP || op > OpCode.JMPLE_L) + { + Assert.ThrowsException(() => script.EmitJump(op, offset_i8)); + Assert.ThrowsException(() => script.EmitJump(op, offset_i32)); + } + else + { + script.EmitJump(op, offset_i8); + script.EmitJump(op, offset_i32); + if ((int)op % 2 == 0) + CollectionAssert.AreEqual(new[] { (byte)op, (byte)offset_i8, (byte)(op + 1) }.Concat(BitConverter.GetBytes(offset_i32)).ToArray(), script.ToArray()); + else + CollectionAssert.AreEqual(new[] { (byte)op }.Concat(BitConverter.GetBytes((int)offset_i8)).Concat(new[] { (byte)op }).Concat(BitConverter.GetBytes(offset_i32)).ToArray(), script.ToArray()); + } + } + } + + [TestMethod] + public void TestEmitPushBigInteger() + { + using (ScriptBuilder script = new()) + { + script.EmitPush(BigInteger.MinusOne); + CollectionAssert.AreEqual(new byte[] { 0x0F }, script.ToArray()); + } + + using (ScriptBuilder script = new()) + { + script.EmitPush(BigInteger.Zero); + CollectionAssert.AreEqual(new byte[] { 0x10 }, script.ToArray()); + } + + for (byte x = 1; x <= 16; x++) + { + using ScriptBuilder script = new(); + script.EmitPush(new BigInteger(x)); + CollectionAssert.AreEqual(new byte[] { (byte)(OpCode.PUSH0 + x) }, script.ToArray()); + } + + CollectionAssert.AreEqual("0080".FromHexString(), new ScriptBuilder().EmitPush(sbyte.MinValue).ToArray()); + CollectionAssert.AreEqual("007f".FromHexString(), new ScriptBuilder().EmitPush(sbyte.MaxValue).ToArray()); + CollectionAssert.AreEqual("01ff00".FromHexString(), new ScriptBuilder().EmitPush(byte.MaxValue).ToArray()); + CollectionAssert.AreEqual("010080".FromHexString(), new ScriptBuilder().EmitPush(short.MinValue).ToArray()); + CollectionAssert.AreEqual("01ff7f".FromHexString(), new ScriptBuilder().EmitPush(short.MaxValue).ToArray()); + CollectionAssert.AreEqual("02ffff0000".FromHexString(), new ScriptBuilder().EmitPush(ushort.MaxValue).ToArray()); + CollectionAssert.AreEqual("0200000080".FromHexString(), new ScriptBuilder().EmitPush(int.MinValue).ToArray()); + CollectionAssert.AreEqual("02ffffff7f".FromHexString(), new ScriptBuilder().EmitPush(int.MaxValue).ToArray()); + CollectionAssert.AreEqual("03ffffffff00000000".FromHexString(), new ScriptBuilder().EmitPush(uint.MaxValue).ToArray()); + CollectionAssert.AreEqual("030000000000000080".FromHexString(), new ScriptBuilder().EmitPush(long.MinValue).ToArray()); + CollectionAssert.AreEqual("03ffffffffffffff7f".FromHexString(), new ScriptBuilder().EmitPush(long.MaxValue).ToArray()); + CollectionAssert.AreEqual("04ffffffffffffffff0000000000000000".FromHexString(), new ScriptBuilder().EmitPush(ulong.MaxValue).ToArray()); + CollectionAssert.AreEqual("050100000000000000feffffffffffffff00000000000000000000000000000000".FromHexString(), new ScriptBuilder().EmitPush(new BigInteger(ulong.MaxValue) * new BigInteger(ulong.MaxValue)).ToArray()); + + Assert.ThrowsException(() => new ScriptBuilder().EmitPush( + new BigInteger("050100000000000000feffffffffffffff0100000000000000feffffffffffffff00000000000000000000000000000000".FromHexString()))); + } + + [TestMethod] + public void TestEmitPushBool() + { + using (ScriptBuilder script = new()) + { + script.EmitPush(true); + CollectionAssert.AreEqual(new byte[] { (byte)OpCode.PUSHT }, script.ToArray()); + } + + using (ScriptBuilder script = new()) + { + script.EmitPush(false); + CollectionAssert.AreEqual(new byte[] { (byte)OpCode.PUSHF }, script.ToArray()); + } + } + + [TestMethod] + public void TestEmitPushReadOnlySpan() + { + using ScriptBuilder script = new(); + var data = new byte[] { 0x01, 0x02 }; + script.EmitPush(new ReadOnlySpan(data)); + + CollectionAssert.AreEqual(new byte[] { (byte)OpCode.PUSHDATA1, (byte)data.Length }.Concat(data).ToArray(), script.ToArray()); + } + + [TestMethod] + public void TestEmitPushByteArray() + { + using (ScriptBuilder script = new()) + { + Assert.ThrowsException(() => script.EmitPush((byte[])null)); + } + + using (ScriptBuilder script = new()) + { + var data = RandomHelper.RandBuffer(0x4C); + + script.EmitPush(data); + CollectionAssert.AreEqual(new byte[] { (byte)OpCode.PUSHDATA1, (byte)data.Length }.Concat(data).ToArray(), script.ToArray()); + } + + using (ScriptBuilder script = new()) + { + var data = RandomHelper.RandBuffer(0x100); + + script.EmitPush(data); + CollectionAssert.AreEqual(new byte[] { (byte)OpCode.PUSHDATA2 }.Concat(BitConverter.GetBytes((short)data.Length)).Concat(data).ToArray(), script.ToArray()); + } + + using (ScriptBuilder script = new()) + { + var data = RandomHelper.RandBuffer(0x10000); + + script.EmitPush(data); + CollectionAssert.AreEqual(new byte[] { (byte)OpCode.PUSHDATA4 }.Concat(BitConverter.GetBytes(data.Length)).Concat(data).ToArray(), script.ToArray()); + } + } + + [TestMethod] + public void TestEmitPushString() + { + using (ScriptBuilder script = new()) + { + Assert.ThrowsException(() => script.EmitPush((string)null)); + } + + using (ScriptBuilder script = new()) + { + var data = RandomHelper.RandString(0x4C); + + script.EmitPush(data); + CollectionAssert.AreEqual(new byte[] { (byte)OpCode.PUSHDATA1, (byte)data.Length }.Concat(Encoding.UTF8.GetBytes(data)).ToArray(), script.ToArray()); + } + + using (ScriptBuilder script = new()) + { + var data = RandomHelper.RandString(0x100); + + script.EmitPush(data); + CollectionAssert.AreEqual(new byte[] { (byte)OpCode.PUSHDATA2 }.Concat(BitConverter.GetBytes((short)data.Length)).Concat(Encoding.UTF8.GetBytes(data)).ToArray(), script.ToArray()); + } + + using (ScriptBuilder script = new()) + { + var data = RandomHelper.RandString(0x10000); + + script.EmitPush(data); + CollectionAssert.AreEqual(new byte[] { (byte)OpCode.PUSHDATA4 }.Concat(BitConverter.GetBytes(data.Length)).Concat(Encoding.UTF8.GetBytes(data)).ToArray(), script.ToArray()); + } + } + } +} diff --git a/tests/Neo.VM.Tests/UT_Slot.cs b/tests/Neo.VM.Tests/UT_Slot.cs new file mode 100644 index 0000000000..ea2f38029b --- /dev/null +++ b/tests/Neo.VM.Tests/UT_Slot.cs @@ -0,0 +1,101 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Slot.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.VM; +using Neo.VM.Types; +using System; +using System.Collections; +using System.Linq; +using System.Numerics; + +namespace Neo.Test +{ + [TestClass] + public class UT_Slot + { + private static Slot CreateOrderedSlot(int count) + { + var check = new Integer[count]; + + for (int x = 1; x <= count; x++) + { + check[x - 1] = x; + } + + var slot = new Slot(check, new ReferenceCounter()); + + Assert.AreEqual(count, slot.Count); + CollectionAssert.AreEqual(check, slot.ToArray()); + + return slot; + } + + public static IEnumerable GetEnumerable(IEnumerator enumerator) + { + while (enumerator.MoveNext()) yield return enumerator.Current; + } + + [TestMethod] + public void TestGet() + { + var slot = CreateOrderedSlot(3); + + Assert.IsTrue(slot[0] is Integer item0 && item0.Equals(1)); + Assert.IsTrue(slot[1] is Integer item1 && item1.Equals(2)); + Assert.IsTrue(slot[2] is Integer item2 && item2.Equals(3)); + Assert.ThrowsException(() => slot[3] is Integer item3); + } + + [TestMethod] + public void TestEnumerable() + { + var slot = CreateOrderedSlot(3); + + BigInteger i = 1; + foreach (Integer item in slot) + { + Assert.AreEqual(item.GetInteger(), i); + i++; + } + + CollectionAssert.AreEqual(new Integer[] { 1, 2, 3 }, slot.ToArray()); + + // Test IEnumerable + + var enumerable = (IEnumerable)slot; + var enumerator = enumerable.GetEnumerator(); + + CollectionAssert.AreEqual(new Integer[] { 1, 2, 3 }, GetEnumerable(enumerator).Cast().ToArray()); + + Assert.AreEqual(3, slot.Count); + + CollectionAssert.AreEqual(new Integer[] { 1, 2, 3 }, slot.ToArray()); + + // Empty + + slot = CreateOrderedSlot(0); + + CollectionAssert.AreEqual(System.Array.Empty(), slot.ToArray()); + + // Test IEnumerable + + enumerable = slot; + enumerator = enumerable.GetEnumerator(); + + CollectionAssert.AreEqual(System.Array.Empty(), GetEnumerable(enumerator).Cast().ToArray()); + + Assert.AreEqual(0, slot.Count); + + CollectionAssert.AreEqual(System.Array.Empty(), slot.ToArray()); + } + } +} diff --git a/tests/Neo.VM.Tests/UT_StackItem.cs b/tests/Neo.VM.Tests/UT_StackItem.cs new file mode 100644 index 0000000000..210fa0d433 --- /dev/null +++ b/tests/Neo.VM.Tests/UT_StackItem.cs @@ -0,0 +1,210 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_StackItem.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.VM; +using Neo.VM.Types; +using System.Numerics; + +namespace Neo.Test +{ + [TestClass] + public class UT_StackItem + { + [TestMethod] + public void TestHashCode() + { + StackItem itemA = "NEO"; + StackItem itemB = "NEO"; + StackItem itemC = "SmartEconomy"; + + Assert.IsTrue(itemA.GetHashCode() == itemB.GetHashCode()); + Assert.IsTrue(itemA.GetHashCode() != itemC.GetHashCode()); + + itemA = new VM.Types.Buffer(1); + itemB = new VM.Types.Buffer(1); + + Assert.IsTrue(itemA.GetHashCode() != itemB.GetHashCode()); + + itemA = true; + itemB = true; + itemC = false; + + Assert.IsTrue(itemA.GetHashCode() == itemB.GetHashCode()); + Assert.IsTrue(itemA.GetHashCode() != itemC.GetHashCode()); + + itemA = 1; + itemB = 1; + itemC = 123; + + Assert.IsTrue(itemA.GetHashCode() == itemB.GetHashCode()); + Assert.IsTrue(itemA.GetHashCode() != itemC.GetHashCode()); + + itemA = new Null(); + itemB = new Null(); + + Assert.IsTrue(itemA.GetHashCode() == itemB.GetHashCode()); + + itemA = new VM.Types.Array(); + + Assert.ThrowsException(() => itemA.GetHashCode()); + + itemA = new Struct(); + + Assert.ThrowsException(() => itemA.GetHashCode()); + + itemA = new Map(); + + Assert.ThrowsException(() => itemA.GetHashCode()); + + itemA = new InteropInterface(123); + itemB = new InteropInterface(123); + + Assert.IsTrue(itemA.GetHashCode() == itemB.GetHashCode()); + + var script = new Script(System.Array.Empty()); + itemA = new Pointer(script, 123); + itemB = new Pointer(script, 123); + itemC = new Pointer(script, 1234); + + Assert.IsTrue(itemA.GetHashCode() == itemB.GetHashCode()); + Assert.IsTrue(itemA.GetHashCode() != itemC.GetHashCode()); + } + + [TestMethod] + public void TestNull() + { + StackItem nullItem = System.Array.Empty(); + Assert.AreNotEqual(StackItem.Null, nullItem); + + nullItem = new Null(); + Assert.AreEqual(StackItem.Null, nullItem); + } + + [TestMethod] + public void TestEqual() + { + StackItem itemA = "NEO"; + StackItem itemB = "NEO"; + StackItem itemC = "SmartEconomy"; + StackItem itemD = "Smarteconomy"; + StackItem itemE = "smarteconomy"; + + Assert.IsTrue(itemA.Equals(itemB)); + Assert.IsFalse(itemA.Equals(itemC)); + Assert.IsFalse(itemC.Equals(itemD)); + Assert.IsFalse(itemD.Equals(itemE)); + Assert.IsFalse(itemA.Equals(new object())); + } + + [TestMethod] + public void TestCast() + { + // Signed byte + + StackItem item = sbyte.MaxValue; + + Assert.IsInstanceOfType(item, typeof(Integer)); + Assert.AreEqual(new BigInteger(sbyte.MaxValue), ((Integer)item).GetInteger()); + + // Unsigned byte + + item = byte.MaxValue; + + Assert.IsInstanceOfType(item, typeof(Integer)); + Assert.AreEqual(new BigInteger(byte.MaxValue), ((Integer)item).GetInteger()); + + // Signed short + + item = short.MaxValue; + + Assert.IsInstanceOfType(item, typeof(Integer)); + Assert.AreEqual(new BigInteger(short.MaxValue), ((Integer)item).GetInteger()); + + // Unsigned short + + item = ushort.MaxValue; + + Assert.IsInstanceOfType(item, typeof(Integer)); + Assert.AreEqual(new BigInteger(ushort.MaxValue), ((Integer)item).GetInteger()); + + // Signed integer + + item = int.MaxValue; + + Assert.IsInstanceOfType(item, typeof(Integer)); + Assert.AreEqual(new BigInteger(int.MaxValue), ((Integer)item).GetInteger()); + + // Unsigned integer + + item = uint.MaxValue; + + Assert.IsInstanceOfType(item, typeof(Integer)); + Assert.AreEqual(new BigInteger(uint.MaxValue), ((Integer)item).GetInteger()); + + // Signed long + + item = long.MaxValue; + + Assert.IsInstanceOfType(item, typeof(Integer)); + Assert.AreEqual(new BigInteger(long.MaxValue), ((Integer)item).GetInteger()); + + // Unsigned long + + item = ulong.MaxValue; + + Assert.IsInstanceOfType(item, typeof(Integer)); + Assert.AreEqual(new BigInteger(ulong.MaxValue), ((Integer)item).GetInteger()); + + // BigInteger + + item = BigInteger.MinusOne; + + Assert.IsInstanceOfType(item, typeof(Integer)); + Assert.AreEqual(new BigInteger(-1), ((Integer)item).GetInteger()); + + // Boolean + + item = true; + + Assert.IsInstanceOfType(item, typeof(VM.Types.Boolean)); + Assert.IsTrue(item.GetBoolean()); + + // ByteString + + item = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 }; + + Assert.IsInstanceOfType(item, typeof(ByteString)); + CollectionAssert.AreEqual(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 }, item.GetSpan().ToArray()); + } + + [TestMethod] + public void TestDeepCopy() + { + Array a = new() + { + true, + 1, + new byte[] { 1 }, + StackItem.Null, + new Buffer(new byte[] { 1 }), + new Map { [0] = 1, [2] = 3 }, + new Struct { 1, 2, 3 } + }; + a.Add(a); + Array aa = (Array)a.DeepCopy(); + Assert.AreNotEqual(a, aa); + Assert.AreSame(aa, aa[^1]); + Assert.IsTrue(a[^2].Equals(aa[^2], ExecutionEngineLimits.Default)); + Assert.AreNotSame(a[^2], aa[^2]); + } + } +} diff --git a/tests/Neo.VM.Tests/UT_Struct.cs b/tests/Neo.VM.Tests/UT_Struct.cs new file mode 100644 index 0000000000..2e62650fc1 --- /dev/null +++ b/tests/Neo.VM.Tests/UT_Struct.cs @@ -0,0 +1,75 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Struct.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.VM; +using Neo.VM.Types; +using System; + +namespace Neo.Test +{ + [TestClass] + public class UT_Struct + { + private readonly Struct @struct; + + public UT_Struct() + { + @struct = new Struct { 1 }; + for (int i = 0; i < 20000; i++) + @struct = new Struct { @struct }; + } + + [TestMethod] + public void TestClone() + { + Struct s1 = new() { 1, new Struct { 2 } }; + Struct s2 = s1.Clone(ExecutionEngineLimits.Default); + s1[0] = 3; + Assert.AreEqual(1, s2[0]); + ((Struct)s1[1])[0] = 3; + Assert.AreEqual(2, ((Struct)s2[1])[0]); + Assert.ThrowsException(() => @struct.Clone(ExecutionEngineLimits.Default)); + } + + [TestMethod] + public void TestEquals() + { + Struct s1 = new() { 1, new Struct { 2 } }; + Struct s2 = new() { 1, new Struct { 2 } }; + Assert.IsTrue(s1.Equals(s2, ExecutionEngineLimits.Default)); + Struct s3 = new() { 1, new Struct { 3 } }; + Assert.IsFalse(s1.Equals(s3, ExecutionEngineLimits.Default)); + Assert.ThrowsException(() => @struct.Equals(@struct.Clone(ExecutionEngineLimits.Default), ExecutionEngineLimits.Default)); + } + + [TestMethod] + public void TestEqualsDos() + { + string payloadStr = new string('h', 65535); + Struct s1 = new(); + Struct s2 = new(); + for (int i = 0; i < 2; i++) + { + s1.Add(payloadStr); + s2.Add(payloadStr); + } + Assert.ThrowsException(() => s1.Equals(s2, ExecutionEngineLimits.Default)); + + for (int i = 0; i < 1000; i++) + { + s1.Add(payloadStr); + s2.Add(payloadStr); + } + Assert.ThrowsException(() => s1.Equals(s2, ExecutionEngineLimits.Default)); + } + } +} diff --git a/tests/Neo.VM.Tests/UT_Unsafe.cs b/tests/Neo.VM.Tests/UT_Unsafe.cs new file mode 100644 index 0000000000..e3b4b8708f --- /dev/null +++ b/tests/Neo.VM.Tests/UT_Unsafe.cs @@ -0,0 +1,33 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Unsafe.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.VM; + +namespace Neo.Test +{ + [TestClass] + public class UT_Unsafe + { + [TestMethod] + public void TestNotZero() + { + Assert.IsFalse(Unsafe.NotZero(System.Array.Empty())); + Assert.IsFalse(Unsafe.NotZero(new byte[4])); + Assert.IsFalse(Unsafe.NotZero(new byte[8])); + Assert.IsFalse(Unsafe.NotZero(new byte[11])); + + Assert.IsTrue(Unsafe.NotZero(new byte[4] { 0x00, 0x00, 0x00, 0x01 })); + Assert.IsTrue(Unsafe.NotZero(new byte[8] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 })); + Assert.IsTrue(Unsafe.NotZero(new byte[11] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 })); + } + } +} diff --git a/tests/Neo.VM.Tests/UT_Utility.cs b/tests/Neo.VM.Tests/UT_Utility.cs new file mode 100644 index 0000000000..e87bcbcda7 --- /dev/null +++ b/tests/Neo.VM.Tests/UT_Utility.cs @@ -0,0 +1,105 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Utility.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.VM; +using System; +using System.Numerics; + +namespace Neo.Test +{ + [TestClass] + public class UT_Utility + { + [TestMethod] + public void TestSqrtTest() + { + Assert.ThrowsException(() => BigInteger.MinusOne.Sqrt()); + + Assert.AreEqual(BigInteger.Zero, BigInteger.Zero.Sqrt()); + Assert.AreEqual(new BigInteger(1), new BigInteger(1).Sqrt()); + Assert.AreEqual(new BigInteger(1), new BigInteger(2).Sqrt()); + Assert.AreEqual(new BigInteger(1), new BigInteger(3).Sqrt()); + Assert.AreEqual(new BigInteger(2), new BigInteger(4).Sqrt()); + Assert.AreEqual(new BigInteger(9), new BigInteger(81).Sqrt()); + } + + private static byte[] GetRandomByteArray(Random random) + { + var byteValue = random.Next(0, 32); + var value = new byte[byteValue]; + + random.NextBytes(value); + return value; + } + + private void VerifyGetBitLength(BigInteger value, long expected) + { + var result = value.GetBitLength(); + Assert.AreEqual(expected, value.GetBitLength(), "Native method has not the expected result"); + Assert.AreEqual(result, Utility.GetBitLength(value), "Result doesn't match"); + } + + [TestMethod] + public void TestGetBitLength() + { + var random = new Random(); + + // Big Number (net standard didn't work) + VerifyGetBitLength(BigInteger.One << 32 << int.MaxValue, 2147483680); + + // Trivial cases + // sign bit|shortest two's complement + // string w/o sign bit + VerifyGetBitLength(0, 0); // 0| + VerifyGetBitLength(1, 1); // 0|1 + VerifyGetBitLength(-1, 0); // 1| + VerifyGetBitLength(2, 2); // 0|10 + VerifyGetBitLength(-2, 1); // 1|0 + VerifyGetBitLength(3, 2); // 0|11 + VerifyGetBitLength(-3, 2); // 1|01 + VerifyGetBitLength(4, 3); // 0|100 + VerifyGetBitLength(-4, 2); // 1|00 + VerifyGetBitLength(5, 3); // 0|101 + VerifyGetBitLength(-5, 3); // 1|011 + VerifyGetBitLength(6, 3); // 0|110 + VerifyGetBitLength(-6, 3); // 1|010 + VerifyGetBitLength(7, 3); // 0|111 + VerifyGetBitLength(-7, 3); // 1|001 + VerifyGetBitLength(8, 4); // 0|1000 + VerifyGetBitLength(-8, 3); // 1|000 + + // Random cases + for (uint i = 0; i < 1000; i++) + { + var bi = new BigInteger(GetRandomByteArray(random)); + Assert.AreEqual(bi.GetBitLength(), Utility.GetBitLength(bi), message: $"Error comparing: {bi}"); + } + + foreach (var bi in new[] { BigInteger.Zero, BigInteger.One, BigInteger.MinusOne, new BigInteger(ulong.MaxValue), new BigInteger(long.MinValue) }) + { + Assert.AreEqual(bi.GetBitLength(), Utility.GetBitLength(bi), message: $"Error comparing: {bi}"); + } + } + + [TestMethod] + public void TestModInverseTest() + { + Assert.ThrowsException(() => BigInteger.One.ModInverse(BigInteger.Zero)); + Assert.ThrowsException(() => BigInteger.One.ModInverse(BigInteger.One)); + Assert.ThrowsException(() => BigInteger.Zero.ModInverse(BigInteger.Zero)); + Assert.ThrowsException(() => BigInteger.Zero.ModInverse(BigInteger.One)); + Assert.ThrowsException(() => new BigInteger(ushort.MaxValue).ModInverse(byte.MaxValue)); + + Assert.AreEqual(new BigInteger(52), new BigInteger(19).ModInverse(141)); + } + } +} diff --git a/tests/Neo.VM.Tests/UT_VMJson.cs b/tests/Neo.VM.Tests/UT_VMJson.cs new file mode 100644 index 0000000000..d91c4b3cad --- /dev/null +++ b/tests/Neo.VM.Tests/UT_VMJson.cs @@ -0,0 +1,85 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_VMJson.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Test.Extensions; +using Neo.Test.Types; +using System; +using System.IO; +using System.Text; + +namespace Neo.Test +{ + [TestClass] + public class UT_VMJson : VMJsonTestBase + { + [TestMethod] + public void TestOthers() => TestJson("./Tests/Others"); + + [TestMethod] + public void TestOpCodesArrays() => TestJson("./Tests/OpCodes/Arrays"); + + [TestMethod] + public void TestOpCodesStack() => TestJson("./Tests/OpCodes/Stack"); + + [TestMethod] + public void TestOpCodesSlot() => TestJson("./Tests/OpCodes/Slot"); + + [TestMethod] + public void TestOpCodesSplice() => TestJson("./Tests/OpCodes/Splice"); + + [TestMethod] + public void TestOpCodesControl() => TestJson("./Tests/OpCodes/Control"); + + [TestMethod] + public void TestOpCodesPush() => TestJson("./Tests/OpCodes/Push"); + + [TestMethod] + public void TestOpCodesArithmetic() => TestJson("./Tests/OpCodes/Arithmetic"); + + [TestMethod] + public void TestOpCodesBitwiseLogic() => TestJson("./Tests/OpCodes/BitwiseLogic"); + + [TestMethod] + public void TestOpCodesTypes() => TestJson("./Tests/OpCodes/Types"); + + private void TestJson(string path) + { + foreach (var file in Directory.GetFiles(path, "*.json", SearchOption.AllDirectories)) + { + Console.WriteLine($"Processing file '{file}'"); + + var realFile = Path.GetFullPath(file); + var json = File.ReadAllText(realFile, Encoding.UTF8); + var ut = json.DeserializeJson(); + + Assert.IsFalse(string.IsNullOrEmpty(ut.Name), "Name is required"); + + if (json != ut.ToJson().Replace("\r\n", "\n")) + { + // Format json + + Console.WriteLine($"The file '{realFile}' was optimized"); + //File.WriteAllText(realFile, ut.ToJson().Replace("\r\n", "\n"), Encoding.UTF8); + } + + try + { + ExecuteTest(ut); + } + catch (Exception ex) + { + throw new AggregateException("Error in file: " + realFile, ex); + } + } + } + } +} diff --git a/tests/Neo.VM.Tests/VMJsonTestBase.cs b/tests/Neo.VM.Tests/VMJsonTestBase.cs new file mode 100644 index 0000000000..85882b7a8e --- /dev/null +++ b/tests/Neo.VM.Tests/VMJsonTestBase.cs @@ -0,0 +1,323 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// VMJsonTestBase.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Test.Extensions; +using Neo.Test.Types; +using Neo.VM; +using Neo.VM.Types; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Neo.Test +{ + public abstract class VMJsonTestBase + { + /// + /// Execute this test + /// + /// Test + public void ExecuteTest(VMUT ut) + { + foreach (var test in ut.Tests) + { + Assert.IsFalse(string.IsNullOrEmpty(test.Name), "Name is required"); + + using TestEngine engine = new(); + Debugger debugger = new(engine); + + if (test.Script.Length > 0) + { + engine.LoadScript(test.Script); + } + + // Execute Steps + + if (test.Steps != null) + { + foreach (var step in test.Steps) + { + // Actions + + if (step.Actions != null) foreach (var run in step.Actions) + { + switch (run) + { + case VMUTActionType.Execute: debugger.Execute(); break; + case VMUTActionType.StepInto: debugger.StepInto(); break; + case VMUTActionType.StepOut: debugger.StepOut(); break; + case VMUTActionType.StepOver: debugger.StepOver(); break; + } + } + + // Review results + + var add = string.IsNullOrEmpty(step.Name) ? "" : "-" + step.Name; + + AssertResult(step.Result, engine, $"{ut.Category}-{ut.Name}-{test.Name}{add}: "); + } + } + } + } + + /// + /// Assert result + /// + /// Engine + /// Result + /// Message + private void AssertResult(VMUTExecutionEngineState result, TestEngine engine, string message) + { + AssertAreEqual(result.State.ToString().ToLowerInvariant(), engine.State.ToString().ToLowerInvariant(), message + "State is different"); + if (engine.State == VMState.FAULT) + { + if (result.ExceptionMessage != null) + { + AssertAreEqual(result.ExceptionMessage, engine.FaultException.Message, message + " [Exception]"); + } + return; + } + AssertResult(result.InvocationStack, engine.InvocationStack, message + " [Invocation stack]"); + AssertResult(result.ResultStack, engine.ResultStack, message + " [Result stack] "); + } + + /// + /// Assert invocation stack + /// + /// Stack + /// Result + /// Message + private void AssertResult(VMUTExecutionContextState[] result, Stack stack, string message) + { + AssertAreEqual(result == null ? 0 : result.Length, stack.Count, message + "Stack is different"); + + int x = 0; + foreach (var context in stack) + { + var opcode = context.InstructionPointer >= context.Script.Length ? OpCode.RET : context.Script[context.InstructionPointer]; + + AssertAreEqual(result[x].NextInstruction, opcode, message + "Next instruction is different"); + AssertAreEqual(result[x].InstructionPointer, context.InstructionPointer, message + "Instruction pointer is different"); + + // Check stack + + AssertResult(result[x].EvaluationStack, context.EvaluationStack, message + " [EvaluationStack]"); + + // Check slots + + AssertResult(result[x].Arguments, context.Arguments, message + " [Arguments]"); + AssertResult(result[x].LocalVariables, context.LocalVariables, message + " [LocalVariables]"); + AssertResult(result[x].StaticFields, context.StaticFields, message + " [StaticFields]"); + + x++; + } + } + + /// + /// Assert result stack + /// + /// Stack + /// Result + /// Message + private void AssertResult(VMUTStackItem[] result, EvaluationStack stack, string message) + { + AssertAreEqual(stack.Count, result == null ? 0 : result.Length, message + "Stack is different"); + + for (int x = 0, max = stack.Count; x < max; x++) + { + AssertAreEqual(ItemToJson(stack.Peek(x)).ToString(Formatting.None), PrepareJsonItem(result[x]).ToString(Formatting.None), message + "Stack item is different"); + } + } + + /// + /// Assert result slot + /// + /// Slot + /// Result + /// Message + private void AssertResult(VMUTStackItem[] result, Slot slot, string message) + { + AssertAreEqual(slot == null ? 0 : slot.Count, result == null ? 0 : result.Length, message + "Slot is different"); + + for (int x = 0, max = slot == null ? 0 : slot.Count; x < max; x++) + { + AssertAreEqual(ItemToJson(slot[x]).ToString(Formatting.None), PrepareJsonItem(result[x]).ToString(Formatting.None), message + "Stack item is different"); + } + } + + private JObject PrepareJsonItem(VMUTStackItem item) + { + var ret = new JObject + { + ["type"] = item.Type.ToString(), + ["value"] = item.Value + }; + + switch (item.Type) + { + case VMUTStackItemType.Null: + { + ret["type"] = VMUTStackItemType.Null.ToString(); + ret.Remove("value"); + break; + } + case VMUTStackItemType.Pointer: + { + ret["type"] = VMUTStackItemType.Pointer.ToString(); + ret["value"] = item.Value.Value(); + break; + } + case VMUTStackItemType.String: + { + // Easy access + + ret["type"] = VMUTStackItemType.ByteString.ToString(); + ret["value"] = Encoding.UTF8.GetBytes(item.Value.Value()); + break; + } + case VMUTStackItemType.ByteString: + case VMUTStackItemType.Buffer: + { + var value = ret["value"].Value(); + Assert.IsTrue(string.IsNullOrEmpty(value) || value.StartsWith("0x"), $"'0x' prefix required for value: '{value}'"); + ret["value"] = value.FromHexString(); + break; + } + case VMUTStackItemType.Integer: + { + // Ensure format + + ret["value"] = ret["value"].Value(); + break; + } + case VMUTStackItemType.Struct: + case VMUTStackItemType.Array: + { + var array = (JArray)ret["value"]; + + for (int x = 0, m = array.Count; x < m; x++) + { + array[x] = PrepareJsonItem(JsonConvert.DeserializeObject(array[x].ToString())); + } + + ret["value"] = array; + break; + } + case VMUTStackItemType.Map: + { + var obj = (JObject)ret["value"]; + + foreach (var prop in obj.Properties()) + { + obj[prop.Name] = PrepareJsonItem(JsonConvert.DeserializeObject(prop.Value.ToString())); + } + + ret["value"] = obj; + break; + } + } + + return ret; + } + + private JToken ItemToJson(StackItem item) + { + if (item == null) return null; + + JToken value; + string type = item.GetType().Name; + + switch (item) + { + case VM.Types.Null _: + { + return new JObject + { + ["type"] = type, + }; + } + case Pointer p: + { + return new JObject + { + ["type"] = type, + ["value"] = p.Position + }; + } + case VM.Types.Boolean v: value = new JValue(v.GetBoolean()); break; + case VM.Types.Integer v: value = new JValue(v.GetInteger().ToString()); break; + case VM.Types.ByteString v: value = new JValue(v.GetSpan().ToArray()); break; + case VM.Types.Buffer v: value = new JValue(v.InnerBuffer.ToArray()); break; + //case VM.Types.Struct v: + case VM.Types.Array v: + { + var jarray = new JArray(); + + foreach (var entry in v) + { + jarray.Add(ItemToJson(entry)); + } + + value = jarray; + break; + } + case VM.Types.Map v: + { + var jdic = new JObject(); + + foreach (var entry in v) + { + jdic.Add(entry.Key.GetSpan().ToArray().ToHexString(), ItemToJson(entry.Value)); + } + + value = jdic; + break; + } + case VM.Types.InteropInterface v: + { + type = "Interop"; + var obj = v.GetInterface(); + + value = obj.GetType().Name.ToString(); + break; + } + default: throw new NotImplementedException(); + } + + return new JObject + { + ["type"] = type, + ["value"] = value + }; + } + + /// + /// Assert with message + /// + /// A + /// B + /// Message + private static void AssertAreEqual(object expected, object actual, string message) + { + if (expected is byte[] ba) expected = ba.ToHexString().ToUpperInvariant(); + if (actual is byte[] bb) actual = bb.ToHexString().ToUpperInvariant(); + + if (expected.ToJson() != actual.ToJson()) + { + throw new Exception(message + + $"{Environment.NewLine}Expected:{Environment.NewLine + expected.ToString() + Environment.NewLine}Actual:{Environment.NewLine + actual.ToString()}"); + } + } + } +}